I’m trying to debug a shell script that uses dialog(1), which has the unfortunate habit of overwriting the debug output I wanted to see. After quite some time banging my head against the wall, I grabbed the source to dialog to look at their examples.
Their examples follow a pattern I have NOT been following; I’m now looking at this trying to understand WTF it does:
1 exec 3>&1 2 returntext=`$DIALOG --title "RANGE BOX" --rangebox "Please set the volume..." 0 60 0 123 5 2>&1 1>&3` 3 returncode=$? 4 exec 3>&-
so… ok. Line 1 duplicates FD 1 (stdout) into FD 3. That’s usually so we can mess with FD 1 later on and still have a handle to stdout. Good so far. Line 2 runs /usr/bin/dialog in a subshell, the options to dialog(1) are irrelevant here. I’m looking at the redirects. “2>&1” duplicates FD 1 into FD 2, effectively throwing away the handle to the inherited /dev/stderr and forcing stderr output onto stdout. That’s a good thing because dialog(1) reports its results onto stderr by default to avoid messing with curses(3) output. “1>&3” however, throws away stdout (FD 1), replacing it with FD 3 which is… already a copy of FD 1 ??? Line 3 is obvious. Line 4 closes FD 3. Not sure that we need to bother, but OK, cleaning up behind ourselves is usually a good thing.
I get the need for swapping stdin/stderr, as dialog(1) – well, really curses(3) - uses stdout to paint the screen, so dialog(1) spits its results out on stderr to avoid conflicting. If we’re in a subshell trying to capture that output, we need to grab it just as dialog(1) exits – and $() assignment only captures stdout.
But what’s the point of having FD 3 in this setup, if all we do is assign it to stdin inside the subshell and then forget about it?
Can anyone see what I’m missing here? -Adam
I think I figured it out…
Command substitution ( $(cmd) or `cmd`) redirects the inner command’s stdout and captures it – that’s the whole point of command substitution. So if I want dialog(1) to talk to the outer shell’s stdout without being captured by command substitution, I have to
1. tell dialog(1) to send its results to stderr – this is the default 2. redirect that diaglog(1) process’s stderr to FD 1 (stdout) using the inner shell 3. redirect that dialog(1) process’s stdout to FD 3, which is the original, outer shell’s stdout, not the captured-by-command-substitution version of stdout. Ugh.
-Adam
From: Roundtable roundtable-bounces@muug.ca On Behalf Of Adam Thompson Sent: Thursday, July 4, 2024 9:01 PM To: MUUG Roundtable roundtable@muug.ca Subject: [RndTbl] dialog(1) shell fd acrobatics question
I’m trying to debug a shell script that uses dialog(1), which has the unfortunate habit of overwriting the debug output I wanted to see. After quite some time banging my head against the wall, I grabbed the source to dialog to look at their examples.
Their examples follow a pattern I have NOT been following; I’m now looking at this trying to understand WTF it does:
1 exec 3>&1 2 returntext=`$DIALOG --title "RANGE BOX" --rangebox "Please set the volume..." 0 60 0 123 5 2>&1 1>&3` 3 returncode=$? 4 exec 3>&-
so… ok. Line 1 duplicates FD 1 (stdout) into FD 3. That’s usually so we can mess with FD 1 later on and still have a handle to stdout. Good so far. Line 2 runs /usr/bin/dialog in a subshell, the options to dialog(1) are irrelevant here. I’m looking at the redirects. “2>&1” duplicates FD 1 into FD 2, effectively throwing away the handle to the inherited /dev/stderr and forcing stderr output onto stdout. That’s a good thing because dialog(1) reports its results onto stderr by default to avoid messing with curses(3) output. “1>&3” however, throws away stdout (FD 1), replacing it with FD 3 which is… already a copy of FD 1 ??? Line 3 is obvious. Line 4 closes FD 3. Not sure that we need to bother, but OK, cleaning up behind ourselves is usually a good thing.
I get the need for swapping stdin/stderr, as dialog(1) – well, really curses(3) - uses stdout to paint the screen, so dialog(1) spits its results out on stderr to avoid conflicting. If we’re in a subshell trying to capture that output, we need to grab it just as dialog(1) exits – and $() assignment only captures stdout.
But what’s the point of having FD 3 in this setup, if all we do is assign it to stdin inside the subshell and then forget about it?
Can anyone see what I’m missing here? -Adam
Cool. Thanks for sharing that.
On Fri, Jul 5, 2024 at 21:33 Adam Thompson athompso@athompso.net wrote:
I think I figured it out…
Command substitution ( $(cmd) or `cmd`) redirects the inner command’s stdout and captures it – that’s the whole point of command substitution.
So if I want dialog(1) to talk to the outer shell’s stdout * without* being captured by command substitution, I have to
- tell dialog(1) to send its results to stderr – this is the default
- redirect that diaglog(1) process’s stderr to FD 1 (stdout) using
the inner shell 3. redirect that dialog(1) process’s stdout to FD 3, which is the *original, outer* shell’s stdout, not the captured-by-command-substitution version of stdout.
Ugh.
-Adam
*From:* Roundtable roundtable-bounces@muug.ca *On Behalf Of *Adam Thompson *Sent:* Thursday, July 4, 2024 9:01 PM *To:* MUUG Roundtable roundtable@muug.ca *Subject:* [RndTbl] dialog(1) shell fd acrobatics question
I’m trying to debug a shell script that uses dialog(1), which has the unfortunate habit of overwriting the debug output I wanted to see.
After quite some time banging my head against the wall, I grabbed the source to dialog to look at their examples.
Their examples follow a pattern I have NOT been following; I’m now looking at this trying to understand WTF it does:
1 exec 3>&1
2 returntext=`$DIALOG --title "RANGE BOX" --rangebox "Please set the volume..." 0 60 0 123 5 2>&1 1>&3`
3 returncode=$?
4 exec 3>&-
so… ok.
Line 1 duplicates FD 1 (stdout) into FD 3. That’s usually so we can mess with FD 1 later on and still have a handle to stdout. Good so far.
Line 2 runs /usr/bin/dialog in a subshell, the options to dialog(1) are irrelevant here. I’m looking at the redirects.
“2>&1” duplicates FD 1 into FD 2, effectively throwing away the handle
to the inherited /dev/stderr and forcing stderr output onto stdout. That’s a good thing because dialog(1) reports its results onto stderr by default to avoid messing with curses(3) output.
“1>&3” however, throws away stdout (FD 1), replacing it with FD 3
which is… already a copy of FD 1 ???
Line 3 is obvious.
Line 4 closes FD 3. Not sure that we need to bother, but OK, cleaning up behind ourselves is usually a good thing.
I get the need for swapping stdin/stderr, as dialog(1) – well, really curses(3) - uses stdout to paint the screen, so dialog(1) spits its results out on stderr to avoid conflicting. If we’re in a subshell trying to capture that output, we need to grab it just as dialog(1) exits – and $() assignment only captures stdout.
But what’s the point of having FD 3 in this setup, if all we do is assign it to stdin inside the subshell and then forget about it?
Can anyone see what I’m missing here?
-Adam
Roundtable mailing list Roundtable@muug.ca https://muug.ca/mailman/listinfo/roundtable
Congratulations, Adam! You've earned a gold star in Unix shell wizardry! 🧙♂️🐱👤🥇
Gilbert
On 2024-07-05 9:32 p.m., Adam Thompson wrote:
I think I figured it out…
Command substitution ( $(cmd) or `cmd`) redirects the inner command’s stdout and captures it – that’s the whole point of command substitution.
So if I want dialog(1) to talk to the outer shell’s stdout /without/ being captured by command substitution, I have to
- tell dialog(1) to send its results to stderr – this is the default
- redirect that diaglog(1) process’s stderr to FD 1 (stdout) using the inner shell
- redirect that dialog(1) process’s stdout to FD 3, which is the /original, outer/ shell’s stdout, not the captured-by-command-substitution version of stdout.
Ugh.
-Adam
*From:*Roundtable roundtable-bounces@muug.ca *On Behalf Of *Adam Thompson *Sent:* Thursday, July 4, 2024 9:01 PM *To:* MUUG Roundtable roundtable@muug.ca *Subject:* [RndTbl] dialog(1) shell fd acrobatics question
I’m trying to debug a shell script that uses dialog(1), which has the unfortunate habit of overwriting the debug output I wanted to see.
After quite some time banging my head against the wall, I grabbed the source to dialog to look at their examples.
Their examples follow a pattern I have NOT been following; I’m now looking at this trying to understand WTF it does:
1 exec 3>&1
2 returntext=`$DIALOG --title "RANGE BOX" --rangebox "Please set the volume..." 0 60 0 123 5 2>&1 1>&3`
3 returncode=$?
4 exec 3>&-
so… ok.
Line 1 duplicates FD 1 (stdout) into FD 3. That’s usually so we can mess with FD 1 later on and still have a handle to stdout. Good so far.
Line 2 runs /usr/bin/dialog in a subshell, the options to dialog(1) are irrelevant here. I’m looking at the redirects.
“2>&1” duplicates FD 1 into FD 2, effectively throwing away the handle to the inherited /dev/stderr and forcing stderr output onto stdout. That’s a good thing because dialog(1) reports its results onto stderr by default to avoid messing with curses(3) output.
“1>&3” however, throws away stdout (FD 1), replacing it with FD 3 which is… already a copy of FD 1 ???
Line 3 is obvious.
Line 4 closes FD 3. Not sure that we need to bother, but OK, cleaning up behind ourselves is usually a good thing.
I get the need for swapping stdin/stderr, as dialog(1) – well, really curses(3) - uses stdout to paint the screen, so dialog(1) spits its results out on stderr to avoid conflicting. If we’re in a subshell trying to capture that output, we need to grab it just as dialog(1) exits – and $() assignment only captures stdout.
But what’s the point of having FD 3 in this setup, if all we do is assign it to stdin inside the subshell and then forget about it?
Can anyone see what I’m missing here?
-Adam