You are on page 1of 9

-----------------------------------------------------------------------------------

---------------------------------------------
In the shell, what does “ 2>&1 ” mean?

Some tricks about redirection


Some syntax particularity about this may have important behaviours. There is
some little samples about redirections, STDERR, STDOUT, and arguments ordering.

###1 - Overwriting or appending? ###

Symbol > mean redirection.

> mean send to as a whole completed file, overwriting target if


exist (see noclobber bash feature at #3 later).
>> mean send in addition to would append to target if exist.

In any case, the file would be created if they not exist.

###2 - The shell command line is order dependent!!

For testing this, we need a simple command which will send something on
both outputs:

$ ls -ld /tmp /tnt


ls: cannot access /tnt: No such file or directory
drwxrwxrwt 118 root root 196608 Jan 7 11:49 /tmp

$ ls -ld /tmp /tnt >/dev/null


ls: cannot access /tnt: No such file or directory

$ ls -ld /tmp /tnt 2>/dev/null


drwxrwxrwt 118 root root 196608 Jan 7 11:49 /tmp

(Expecting you don't have a directory named /tnt, of course ;). Well,
we have it!!

So, let's see:

$ ls -ld /tmp /tnt >/dev/null


ls: cannot access /tnt: No such file or directory

$ ls -ld /tmp /tnt >/dev/null 2>&1

$ ls -ld /tmp /tnt 2>&1 >/dev/null


ls: cannot access /tnt: No such file or directory

The last command line dumps STDERR to the console, and it seem not to
be the expected behaviour... But...

If you want to make some post filtering about standard output, error
output or both:

$ ls -ld /tmp /tnt | sed 's/^.*$/<-- & --->/'


ls: cannot access /tnt: No such file or directory
<-- drwxrwxrwt 118 root root 196608 Jan 7 12:02 /tmp --->

$ ls -ld /tmp /tnt 2>&1 | sed 's/^.*$/<-- & --->/'


<-- ls: cannot access /tnt: No such file or directory --->
<-- drwxrwxrwt 118 root root 196608 Jan 7 12:02 /tmp --->
$ ls -ld /tmp /tnt >/dev/null | sed 's/^.*$/<-- & --->/'
ls: cannot access /tnt: No such file or directory

$ ls -ld /tmp /tnt >/dev/null 2>&1 | sed 's/^.*$/<-- & --->/'

$ ls -ld /tmp /tnt 2>&1 >/dev/null | sed 's/^.*$/<-- & --->/'


<-- ls: cannot access /tnt: No such file or directory --->

Notice that the last command line in this paragraph is exactly same as
in previous paragraph, where I wrote seem not to be the expected behaviour (so,
this could even be an expected behaviour).

Well, there is a little tricks about redirections, for doing different


operation on both outputs:

$ ( ls -ld /tmp /tnt | sed 's/^/O: /' >&9 ) 9>&2 2>&1 | sed
's/^/E: /'
O: drwxrwxrwt 118 root root 196608 Jan 7 12:13 /tmp
E: ls: cannot access /tnt: No such file or directory
Nota: &9 descriptor would occur spontaneously because of ) 9>&2.

Addendum: nota! With the new version of bash (>4.0) there is a new
feature and more sexy syntax for doing this kind of things:

$ ls -ld /tmp /tnt 2> >(sed 's/^/E: /') > >(sed 's/^/O: /')
O: drwxrwxrwt 17 root root 28672 Nov 5 23:00 /tmp
E: ls: cannot access /tnt: No such file or directory

And finally for such a cascading output formatting:

$ ((ls -ld /tmp /tnt |sed 's/^/O: /' >&9 ) 2>&1 |sed 's/^/E: /') 9>&1|
cat -n
1 O: drwxrwxrwt 118 root root 196608 Jan 7 12:29 /tmp
2 E: ls: cannot access /tnt: No such file or directory

Addendum: nota! Same new syntax, in both ways:

$ cat -n <(ls -ld /tmp /tnt 2> >(sed 's/^/E: /') > >(sed 's/^/O: /'))
1 O: drwxrwxrwt 17 root root 28672 Nov 5 23:00 /tmp
2 E: ls: cannot access /tnt: No such file or directory

Where STDOUT go through a specific filter, STDERR to another and


finally both outputs merged go through a third command filter.

###3 - A word about noclobber option and >| syntax###

That's about overwriting:

While set -o noclobber instruct bash to not overwrite any existing


file, the >| syntax let you pass through this limitation:

$ testfile=$(mktemp /tmp/testNoClobberDate-XXXXXX)

$ date > $testfile ; cat $testfile


Mon Jan 7 13:18:15 CET 2013

$ date > $testfile ; cat $testfile


Mon Jan 7 13:18:19 CET 2013
$ date > $testfile ; cat $testfile
Mon Jan 7 13:18:21 CET 2013
The file is overwritten each time, well now:

$ set -o noclobber

$ date > $testfile ; cat $testfile


bash: /tmp/testNoClobberDate-WW1xi9: cannot overwrite existing
file
Mon Jan 7 13:18:21 CET 2013

$ date > $testfile ; cat $testfile


bash: /tmp/testNoClobberDate-WW1xi9: cannot overwrite existing
file
Mon Jan 7 13:18:21 CET 2013
Pass through with >|:

$ date >| $testfile ; cat $testfile


Mon Jan 7 13:18:58 CET 2013

$ date >| $testfile ; cat $testfile


Mon Jan 7 13:19:01 CET 2013
Unsetting this option and/or inquiring if already set.

$ set -o | grep noclobber


noclobber on

$ set +o noclobber

$ set -o | grep noclobber


noclobber off

$ date > $testfile ; cat $testfile


Mon Jan 7 13:24:27 CET 2013

$ rm $testfile

###4 - Last trick and more...###

For redirecting both output from a given command, we see that a right
syntax could be:

$ ls -ld /tmp /tnt >/dev/null 2>&1


for this special case, there is a shortcut syntax: &> ... or >&

$ ls -ld /tmp /tnt &>/dev/null

$ ls -ld /tmp /tnt >&/dev/null


Nota: if 2>&1 exist, 1>&2 is a correct syntax too:

$ ls -ld /tmp /tnt 2>/dev/null 1>&2


###4b- Now, I will let you think about:

$ ls -ld /tmp /tnt 2>&1 1>&2 | sed -e s/^/++/


++/bin/ls: cannot access /tnt: No such file or directory
++drwxrwxrwt 193 root root 196608 Feb 9 11:08 /tmp/

$ ls -ld /tmp /tnt 1>&2 2>&1 | sed -e s/^/++/


/bin/ls: cannot access /tnt: No such file or directory
drwxrwxrwt 193 root root 196608 Feb 9 11:08 /tmp/

###4c- If you're interested in more information

You could read the fine manual by hitting:

man -Len -Pless\ +/^REDIRECTION bash

in a bash console ;-)

(https://i.stack.imgur.com/huKF2.png)

(http://www.catonmat.net/blog/bash-one-liners-explained-part-three/)
-----------------------------------------------------------------------------------
---------------------------------------------

-----------------------------------------------------------------------------------
---------------------------------------------
Write output of bash script to file with date

bash script.sh > "/var/log/$(date +%Y-%m-%d_%H:%M).log"

Out put date in bash


# cat showdate.sh
#!/bin/bash
echo "Date is: `date`"
-----------------------------------------------------------------------------------
---------------------------------------------

-----------------------------------------------------------------------------------
---------------------------------------------
In a Bash script, how can I exit the entire script if a certain condition occurs?
exit 1

Reserved Exit Codes

Code Meaning
Example
Comments
1 Catchall for general errors let "var1 =
1/0" Miscellaneous errors, such as "divide by zero" and other
impermissible operations
2 Misuse of shell builtins
empty_function() {} Missing keyword or command, or permission
problem
126 Command invoked cannot execute /dev/null
Permission problem or command is not an executable
127 "command not found"
illegal_command Possible problem with $PATH or a typo
128 Invalid argument to exit exit 3.14159
exit takes only integer args in the range 0 - 255
128+n Fatal error signal "n" kill -9 $PPID of
script $? returns 137 (128 + 9)
130 Script terminated by Control-C Ctl-C Control-C
is fatal error signal 2, (130 = 128 + 2, see above)
255* Exit status out of range exit -1
exit takes only integer args in the range 0 - 255
-----------------------------------------------------------------------------------
---------------------------------------------

-----------------------------------------------------------------------------------
---------------------------------------------
How to call one shell script from another shell script?
I have two shell scripts, a.sh and b.sh.
How can I call b.sh from within the shell script a.sh?

There are a couple of different ways you can do this:

1. Make the other script executable, add the #!/bin/bash line at the
top, and the path where the file is to the $PATH environment variable. Then you can
call it as a normal command;

2. Or call it with the source command (alias is .) like this: source


/path/to/script;

3. Or use the bash command to execute it: /bin/bash /path/to/script;

The first and third methods execute the script as another process, so
variables and functions in the other script will not be accessible.
The second method executes the script in the first script's process, and
pulls in variables and functions from the other script so they are usable from the
calling script.

In the second method, if you are using exit in second script, it will exit
the first script as well. Which will not happen in first and third methods.

OR

#!/bin/bash
echo "This script is about to run another script."
sh ./script.sh
#This assumes that script.sh is in the same directory as the whatever
script is running. If you wanted to call a script somewhere else, you would say sh
<path to script>/script.sh
echo "This script has just run another script."

OR

There are a couple of ways you can do this. Terminal to execute the script:

#!/bin/bash
SCRIPT_PATH="/path/to/script.sh"

# Here you execute your script


"$SCRIPT_PATH"
# or
. "$SCRIPT_PATH"

# or
source "$SCRIPT_PATH"

# or
bash "$SCRIPT_PATH"

# or
eval '"$SCRIPT_PATH"'

# or
OUTPUT=$("$SCRIPT_PATH")
echo $OUTPUT

# or
OUTPUT=`"$SCRIPT_PATH"`
echo $OUTPUT

# or
("$SCRIPT_PATH")

# or
(exec "$SCRIPT_PATH")
All this is correct for the path with spaces!!!
-----------------------------------------------------------------------------------
---------------------------------------------

-----------------------------------------------------------------------------------
---------------------------------------------
How can I check if a directory exists in a Bash shell script?

symbolic links may have to be treated differently, if subsequent commands


expect directories:

if [ -d "$LINK_OR_DIR" ]; then
if [ -L "$LINK_OR_DIR" ]; then
# It is a symlink!
# Symbolic link specific commands go here.
rm "$LINK_OR_DIR"
else
# It's a directory!
# Directory command goes here.
rmdir "$LINK_OR_DIR"
fi
fi

With the same syntax you can use:

-e: any kind of archive


-f: file
-h: symbolic link
-r: readable file
-w: writable file
-x: executable file
-s: file size greater than zero
-d: check if it's a directory
-L: check if it's a symbolic link
-----------------------------------------------------------------------------------
---------------------------------------------

-----------------------------------------------------------------------------------
---------------------------------------------
Each field in a /etc/passwd line is separated by a colon, and the username is the
first field, so you need to filter each line to only show the characters up to the
first colon

grep -oE '^[^:]+' /etc/passwd

-o tells it to only return the part of the line that matches.


-E turns on extended regular expressions so the + will work later.
^ matches the beginning of the line,
[^:] matches anything except a colon, and + means as many characters as
possible. So this will match the beginning of every line up until the first colon

If you're able to use other tools besides grep, here are other generally better
ways of doing it:

cut -d: -f1 /etc/passwd


sed 's/:.*//' /etc/passwd
awk -F: '{print $1}' /etc/passwd

grep -o '^[a-zA-Z]*:' /etc/passwd


grep -o '^[a-zA-Z]*:' /etc/passwd

-----------------------------------------------------------------------------------
---------------------------------------------

-----------------------------------------------------------------------------------
---------------------------------------------
To write the output of a command to a file, there are basically 10 commonly used
ways.

Overview:
Please note that the n.e. in the syntax column means "not existing".
There is a way, but it's too complicated to fit into the column. You can find
a helpful link in the List section about it.

----------++---------------------++---------------------++------------
|| visible in terminal || visible in file || existing
----------++---------------------++---------------------++------------
Syntax || StdOut | StdErr || StdOut | StdErr || file
----------++----------+----------++----------+----------++------------
> || no | yes || yes | no || overwrite
>> || no | yes || yes | no || append
----------++----------+----------++----------+----------++------------
2> || yes | no || no | yes || overwrite
2>> || yes | no || no | yes || append
----------++----------+----------++----------+----------++------------
&> || no | no || yes | yes || overwrite
&>> || no | no || yes | yes || append
----------++----------+----------++----------+----------++------------
| tee || yes | yes || yes | no || overwrite
| tee -a || yes | yes || yes | no || append
----------++----------+----------++----------+----------++------------
n.e. (*) || yes | yes || no | yes || overwrite
n.e. (*) || yes | yes || no | yes || append
----------++----------+----------++----------+----------++------------
|& tee || yes | yes || yes | yes || overwrite
|& tee -a || yes | yes || yes | yes || append
----------++----------+----------++----------+----------++------------

List:
1. command > output.txt

The standard output stream will be redirected to the file only, it will
not be visible in the terminal. If the file already exists, it gets overwritten.

2. command >> output.txt

The standard output stream will be redirected to the file only, it will
not be visible in the terminal. If the file already exists, the new data will get
appended to the end of the file.

3. command 2> output.txt

The standard error stream will be redirected to the file only, it will
not be visible in the terminal. If the file already exists, it gets overwritten.

4. command 2>> output.txt

The standard error stream will be redirected to the file only, it will
not be visible in the terminal. If the file already exists, the new data will get
appended to the end of the file.

5. command &> output.txt

Both the standard output and standard error stream will be redirected
to the file only, nothing will be visible in the terminal. If the file already
exists, it gets overwritten.

6. command &>> output.txt

Both the standard output and standard error stream will be redirected
to the file only, nothing will be visible in the terminal. If the file already
exists, the new data will get appended to the end of the file..

7. command | tee output.txt

The standard output stream will be copied to the file, it will still be
visible in the terminal. If the file already exists, it gets overwritten.

8. command | tee -a output.txt

The standard output stream will be copied to the file, it will still be
visible in the terminal. If the file already exists, the new data will get appended
to the end of the file.

9. (*)

Bash has no shorthand syntax that allows piping only StdErr to a second
command, which would be needed here in combination with tee again to complete the
table. If you really need something like that, please look at "How to pipe stderr,
and not stdout?" on Stack Overflow for some ways how this can be done e.g. by
swapping streams or using process substitution.

10. command |& tee output.txt

Both the standard output and standard error streams will be copied to
the file while still being visible in the terminal. If the file already exists, it
gets overwritten.

11. command |& tee -a output.txt

Both the standard output and standard error streams will be copied to
the file while still being visible in the terminal. If the file already exists, the
new data will get appended to the end of the file.
-----------------------------------------------------------------------------------
---------------------------------------------

You might also like