I use the Bash shell every day. It’s one of the most useful tools I know to get stuff done, and, as such, it’s worth studying.
One of the resources I’ve read inspired me to start documenting some of the more advanced things that Bash can do, esoteric or otherwise, lest I forget. After all, if I don’t use something close to every day, it tends to get all foggy and gray :)
These are not in order of any importance, and I’ll be continually adding to this post.
Contents
Heredocs
Standard Example
$ foo="i am foo"
$ cat <<EOF
hello
$foo
goodbye
EOF
Will expand the parameter $foo
to print “i am foo”.
hello
i am foo
goodbye
Preventing Parameter Expansion (‘LimitString’, \LimitString)
$ foo="i am foo"
$ cat <<\EOF
hello
$foo
goodbye
EOF
Will print the literal string $foo
.
hello
$foo
goodbye
Ignore Leading Tabs (-LimitString)
$ cat <<-EOF
hello
i am foo
goodbye
EOF
Results in the tabs being preserved:
hello
i am foo
goodbye
It can be argued that this is more pleasant to read. The closing limit string EOF
still cannot be preceded by any whitespace, though.
“Anonymous” heredoc
The anonymous, or placeholder, command :
can be useful to comment-out whole code blocks. I can see using this when I’m doing a lot of shell scripting, which I do from time to time, otherwise it’s just an interesting tidbit.
In this example, the for
loop won’t run because it is within the “anonymous” heredoc
.
#!/bin/bash echo hello world : <<\EOF for i in {1..10} do echo $i done EOF echo goodbye cruel world
The backslash before the limit string
EOF
isn’t necessarily needed, but there could be instances where the “anonymous”heredoc
is wrapping code that contains a bracket ({}
), which I’ve read could make Bash barf all over itself.I haven’t been able to reproduce this, so maybe it’s only older versions of Bash, but it’s better to be safe than sorry!
I usually comment-out whole code blocks using a Vim macro that I wrote (essentially, s/^/#/g
in a visual block), but I might change my ways.
I/O Redirection
Each process gets three default files:
stdin
stdout
stderr
They are referenced by their file descriptors, 0, 1 and 2, respectively.
>
,<
,>>
and<<
are the redirection operators.
stdout
These all produce a zero-length file:
$ touch foo.txt
$ > foo.txt
$ 1> foo.txt
$ : > foo.txt
$ <> foo.txt
When redirecting a directory listing to a file, it’s not necessary to explicitly use stdout
’s file descriptor as it’s assumed. For example, the following commands are equivalent:
$ ls > foo.txt
$ ls 1> foo.txt
To append, use
>>
.
Weeeeeeeeeeeeeeeeee
stderr
Redirect stderr
to a file:
2> foo.txt
For example, using a “bad” command will redirect the error to a file rather than displaying it on the screen:
$ asdf 2> foo.txt
$ cat foo.txt
Command 'asdf' not found, did you mean:
command 'asdfg' from deb aoeui
command 'sadf' from deb sysstat
command 'sdf' from deb sdf
command 'adsf' from deb ruby-adsf
Try: sudo apt install <deb name>
Take note that the following might not do as you expect:
$ asdf > foo.txt
It will create the file foo.txt
, if it doesn’t already exist, but it will be empty!
To redirect both
stdout
andstderr
:&> foo.txt
Redirecting File Descriptors
Redirect stderr
to stdout
:
2>&1
Append both stderr
and stdout
to foo.txt
:
$ asdf >> foo.txt 2>&1
Random access*:
$ echo 123456789 > foo.txt # Create `foo.txt`. $ exec 3<> foo.txt # Assign fd 3 for reading and writing. $ read -n 4 <&3 # Seek 4 chars. $ echo -n . >&3 # Write. $ cat foo.txt # 1234.6789 $ cat <&3 # 6789 $ exec 3>&- # Close fd 3.
Closing File Descriptors*
n<&- Close input file descriptor n. n>&- Close output file descriptor n. 0<&-, <&- Close stdin. 1>&-, >&- Close stdout.
* Examples taken from http://tldp.org/LDP/abs/html/io-redirection.html
Multiple Instances of Input and Output Redirection (and Pipes)
Common:
command < input-file > output-file
Uncommon, but equivalent:
< input-file command > output-file
These commands are all equivalent:
$ grep hugo < <(ls -R) > foo.txt
$ < <(ls -R) grep hugo > foo.txt
$ ls -R | grep hugo > foo.txt
Redirecting Code Blocks
The following are equivalent:
$ while read name
> do
> echo $name
> done < <(echo -e "john\npaul\ngeorge\nringo")
$ echo -e "john\npaul\ngeorge\nringo" | while read name
> do
> echo $name
> done
john
paul
george
ringo
Subshells
- Builtins do not launch a new process, but external commands do.
- Any variables created in a subshell are only scoped to that process, so they are not visible in the parent shell.
A command list within parentheses will launch and execute within a subshell.
$ ( cd /my_project && make )
$
You can see the nesting level by printing the value of the $BASH_SUBSHELL
internal variable:
$ echo $BASH_SUBSHELL
0
$ ( cd /etc ; echo $BASH_SUBSHELL ; ( cd /proc ; echo $BASH_SUBSHELL ) )
1
2
$ echo $BASH_SUBSHELL
0
Note that the commands executed in a subshell, so the current directory didn’t change in the parent process. This can be useful in a script when needing to frequently change directories, although some purists don’t like to spawn a subshell (also, there’s pushd and popd).
You could run a command group in a subshell with its own environment*:
COMMAND1
COMMAND2
COMMAND3
(
IFS=:
PATH=/bin
unset TERMINFO
set -C
shift 5
COMMAND4
COMMAND5
exit 3 # Only exits the subshell!
)
# The parent shell has not been affected, and the environment is preserved.
COMMAND6
COMMAND7
* Example taken from http://tldp.org/LDP/abs/html/subshells.html
Restricted Shell
To run a shell or script in a more secure environment, invoke the process as a restricted shell.
Start Bash as:
-
From the command line
- rbash
- bash -r
- bash –restricted
-
Shell options
- set -r
- set –restricted
/dev
Bash has a builtin pseudo-device file, /dev/tcp
, which creates a TCP
connection to the associated socket. The format is:
/dev/tcp/$HOST/$PORT
Get a web page:
$ exec 5<> /dev/tcp/www.benjamintoll.com/80
$ echo -e "GET / HTTP/1.0\n" >&5
$ cat <&5
$ exec 5<&-
This is mostly the same as:
curl -O www.benjamintoll.com/index.html
Get headers:
$ exec 5<> /dev/tcp/www.benjamintoll.com/80
$ echo -e "GET / HTTP/1.0\n" >&5
$ cat <&5
$ exec 5<&-
Emulate a ping:
echo "HEAD / HTTP/1.0" > /dev/tcp/chomsky/80
echo $?
Etc.
Weeeeeeeeeeeeeeeeeeeee
Of course, you can specify more request headers in the
echo
command, it’s merely “talking” theHTTP
protocol, just as you would with anytelnet
session (or other).
Options
The set
command enables or disables options within a script, shell or Bash file (i.e., .bashrc
).
To enable:
set -o verbose
or
set -v
To disable:
set +o verbose
or
set +v
Here are some other ways to set options:
-
After the shebang:
#!/bin/bash -v
-
When invoking a script:
$ bash -v script_name or $ bash -o verbose script_name