Bash script stderr and stdout

325 views Asked by At

First, i'm not sure if i'm formulating the question correctly. So I have a bash script that delete a user in a system. The problem is, it is showing the err msg before the terminal code. Here. Code:

echo -e "\nCommencing user $UserName removal"
echo -e "Deactivating $UserName shell account ..." "$(chsh -s /usr/bin/false $UserName)"
echo -e "Listing group(s) user: " "$(groups $UserName)"
echo -e "Removing Crontab ... " "$(crontab -r -u $UserName)"

Here is the output:

Commencing user Test2 removal
chsh: unknown user: Test2
Deactivating Test2 shell account ...
groups: Test2: no such user
Listing group(s) user:  
scripts/sudo.sh: line 332: /delete_Test2/crontab.bak: No such file or directory
Saving Crontab ...  

The user to delete is Test2, which is this case "supposedly" does not exist (a different question for a different time). Now, shouldn't the stderr msg display next to the command or below it instead of above it?

Thanks in advance

3

There are 3 answers

1
Gordon Davisson On BEST ANSWER

When the shell executes the line echo -e "Deactivating $UserName shell account ..." "$(chsh -s /usr/bin/false $UserName)", here's the sequence of events:

  1. The shell runs chsh -s /usr/bin/false Test2, with its stdout going to a capture buffer so the shell can use it later.
  2. chsh discover's that "Test2" doesn't exist, and prints "chsh: unknown user: Test2" to its stderr. Since the shell didn't do anything special with its stderr, this goes directly to the shell's stderr, which is your terminal.
  3. chsh exits
  4. The shell takes the captured output (note: stdout, not stderr) from chsh (there wasn't any), and substitutes it into the echo command line, giving echo -e "Deactivating Test2 shell account ..." ""

Note that the error message gets printed at step 2, but the message about what's supposedly about to happen doesn't get printed until step 4.

There are several ways to solve this; generally the best is to avoid the whole mess of capturing the command's output, then echoing it. It's pointless, and just leads to confusions like this (and some others you haven't run into). Just run the command directly, and let its output (both stdout and stderr) go to their normal places, in normal order.

BTW, I also recommend avoiding echo -e, or indeed echo -anything. The POSIX standard for echo says "Implementations shall not support any options." In fact, some implementations do support options; others just treat them as part of the string to be printed. Also, some interpret escape sequences (like \n) in the strings to print, some don't, and some only do if -e is specified (violating the POSIX standard). Given how unpredictable these features are, it's best to just avoid such iffy situations, and either use printf instead (which is more complicated to use, but much more predictable), or (as in your case) just use a separate echo command for each line.

Also, you should almost always double-quote variable references (e.g. groups "$UserName" instead of groups $UserName), just in case they contain spaces, wildcards, etc.

Based on the above, here's how I'd write the script:

echo    # print a blank line
echo "Commencing user $UserName removal"
echo "Deactivating $UserName shell account ..."
chsh -s /usr/bin/false "$UserName"

echo "Listing group(s) user: "
groups "$UserName"

echo "Removing Crontab ... "
crontab -r -u "$UserName"
0
Ignacio Vazquez-Abrams On

Now, shouldn't the stderr msg display next to the command or below it instead of above it?

No, because the command is executed first and then its output is echoed. Perform the echo and command in two separate lines.

0
codeforester On

That's because chsh is writing to stderr before bash has a chance to write to stdout. Redirect stderr to stdout to get it right:

echo -e "Deactivating $UserName shell account ..." "$(chsh -s /usr/bin/false $UserName 2>&1)"

In general, it is better to check if the user exists before trying to deactivate it.