Difference between revisions of "Bash notes"
Line 14: | Line 14: | ||
after the 'END_HEREDOC' and on its own line. | after the 'END_HEREDOC' and on its own line. | ||
Also note that you have to use th 'printf' command. | Also note that you have to use th 'printf' command. | ||
− | The echo command or the echo | + | The echo command or the echo builtin will remove new-lines. |
END_HEREDOC | END_HEREDOC | ||
) | ) |
Revision as of 16:48, 28 March 2010
Contents
- 1 Assign variable the contents of a here document
- 2 Check if a web page exists or not
- 3 Draw a circle in ASCII
- 4 Clear all environment variables
- 5 Redirect entire output of a script from inside the script itself
- 6 Turn off bash history for a session
- 7 Rename a group of files by extension
- 8 Usage Function
- 9 Special Shell Variables
- 10 Variable Expansion and Substitution
- 11 absolute and relative paths
- 12 Statements
- 13 read -- get input from user
- 14 check if running as root
- 15 check if process is running
Assign variable the contents of a here document
Here documents or here files can be put into a variable.
#!/bin/sh EXIT_MESSAGE=$(cat <<'END_HEREDOC' This documents what this script does. You don't have to worry about embedded "quotes". This makes it east to read in the script and easy to print. Note that the 'END_HEREDOC' is quoted above. Also the final closing parenthesis must come after the 'END_HEREDOC' and on its own line. Also note that you have to use th 'printf' command. The echo command or the echo builtin will remove new-lines. END_HEREDOC ) printf "%s\n" "$EXIT_MESSAGE" exit 0
Check if a web page exists or not
There must be a better way than this. I was surprised that curl doesn't offer an option to detect HTTP response.
curl --silent --no-buffer -I http://www.example.org/foobar.html | grep -iq "200 OK"
Draw a circle in ASCII
This uses `awk` for the math. The sequence in incremented by a fraction, 0.4, each time so that there is some overlap to make the circle smoother. You could use `seq 1 57`, but there will be gaps and rough edges.
tput clear;(seq 1 .4 57|awk '{x=int(11+10*cos($1/9));y=int(22+20*sin($1/9));system("tput cup "x" "y";echo X")}');tput cup 22 0
Some systems don't have the `seq` command. The following will work on a greater variety of platforms:
tput clear;(yes|head -n 114|cat -n|awk '{x=int(11+10*cos($1/18));y=int(22+20*sin($1/18));system("tput cup "x" "y";echo X")}');tput cup 22 0
That will render this fine quality circle (fits on an 80x24 console):
XXXXXXXXXXXXXXXXXX XXXX XXX XX XXX XX XX XX X XX XX XX X X X X X X X X X X X X X XX X XX XX XX XX XX XX XX XX XXXX XXX XXXXXXXXXXXXXXXXXX
There should be a way to do it without `awk`... Maybe `join` or `paste` would help in this case. Here is a start:
seq 1 56 | sed -e 's/\(.*\)/c(\1 \/ 9)/' | bc
Then it just starts to get silly:
tput clear;(seq 1 0.4 57|awk '{x=int(11+10*cos($1/9));y=int(22+20*sin($1/9));system("tput cup "x" "y";echo X")}');tput cup 8 15;echo X;tput cup 8 28;echo X;(seq 16 0.4 21.6|awk '{x=int(11+6*cos($1/3));y=int(22+12*sin($1/3));system("tput cup "x" "y";echo X")}');tput cup 22 0
Clear all environment variables
This will delete all environment variables except for a few explicitly allowed to stay.
unset $(env | grep -o '^[_[:alpha:]][_[:alnum:]]*' | grep -v -E '^PWD$|^USER$|^TERM$|^SSH_.*|^LC_.*')
Redirect entire output of a script from inside the script itself
#!/bin/sh # This demonstrates printing and logging output at the same time. # This works by starting `tee` in the background with its stdin # coming from a named pipe that we make; then we redirect our # stdout and stderr to the named pipe. All pipe cleanup is handled # in a trap at exit. # This is the exit trap handler for the 'tee' logger. on_exit_trap_cleanup () { # Close stdin and stdout which closes our end of the pipe # and tells `tee` we are done. exec 1>&- 2>&- # Wait for `tee` process to finish. If we exited here then the `tee` # process might get killed before it hand finished flushing its buffers # to the logfile. wait $TEEPID rm ${PIPEFILE} } tee_log_output () { LOGFILE=$1 PIPEFILE=$(mktemp -u $(basename $0)-pid$$-pipe-XXX) mkfifo ${PIPEFILE} tee ${LOGFILE} < ${PIPEFILE} & TEEPID=$! # Redirect subsequent stdout and stderr output to named pipe. exec > ${PIPEFILE} 2>&1 trap on_exit_trap_cleanup EXIT } LOGFILE="$0-$$.log" echo "Logging stdin and stderr output to logfile: ${LOGFILE}" tee_log_output ${LOGFILE} date --rfc-3339=seconds echo "command: $0" echo "pid: $$" sleep 2 date --rfc-3339=seconds
This works only in Bash 4.x.
#!/bin/sh # This will send output to a log file and to the screen using an # unamed pipe to `tee`. This works only in Bash 4.x. exec > >(tee -a ${LOGFILE}) date --rfc-3339=seconds echo "command: $0" echo "pid: $$" sleep 1 date --rfc-3339=seconds
Turn off bash history for a session
set +o history
Rename a group of files by extension
For example, rename all images from foo.jpg to foo_2.jpg.
This is somewhat more clear:
for filename in *.jpg ; do mv $filename `basename $filename .jpg`_2.jpg; done
This is more "correct" and doesn't require `basename`:
for filename in *.jpg ; do mv $filename ${filename%.jpg}_2.jpg; done
Usage Function
exit_with_usage() { local EXIT_CODE="${1:-0}" if [ ${EXIT_CODE} -eq 1 ]; then exec 1>&2 fi echo "TODO: This script does something useful." echo "Usage: $0 [-h | --help]" echo " -h --help : Shows this help." exit "${EXIT_CODE}" }
Special Shell Variables
- $*
- all parameters separated by the first character of $IFS
- $@
- all parameters quoted
- $#
- the number of parameters
- $-
- option flags set `set` or passed to shell
- $?
- exit status of last command
- $!
- pid of last background command
- $$
- pid of this script or shell
- $0
- name of this script of shell
- $_
- arguments of last command (with variables expanded).
Variable Expansion and Substitution
Bash can do some freaky things with variables. It can do lots of other substitutions. See "Parameter Expansion" in the Bash man page.
- ${foo#pattern} - deletes the shortest possible match from the left
- ${foo##pattern} - deletes the longest possible match from the left
- ${foo%pattern} - deletes the shortest possible match from the right
- ${foo%%pattern} - deletes the longest possible match from the right
- ${foo=text} - If $foo exists and is not null then return $foo. If $foo doesn't exist then create it and set value to text.
brace expansion versus backtick expansion for command substitution
Backtick expansion works in even the oldest Bourne shell variant. It cannot best nested without quoting.
echo `ls /boot/`
Brace expansion works in any POSIX Bourne shell (sh, ash, dash, bash, etc...).
echo $(ls /boot/*$(uname -r)*)
Although you can do it if you quote the inner backticks:
echo `ls /boot/*\`uname -r\`*`
quote output in echo to preserve newlines
Echo converts newlines to spaces. This can be useful for substituting in loops. Quoting the argument will preserve the newlines.
This converts newlines to spaces:
echo $(ls /boot/)
The following preserves the newlines output from `ls`:
echo "$(ls /boot/)"
absolute and relative paths
Convert a relative path to a absolute path. It is stupid that there is not a command to do this. This does not effect the current working directory. This finds the absolute full path to $1:
echo "absolute path: `cd $1; pwd`"
Get the absolute path of the currently running script.
abs_path_here=`echo -n \`pwd\` ;( [ \`dirname \$0\` == '.' ] && echo ) || echo “/\`dirname \$0\`”`
Statements
Loop on filenames in a directory
for foo in *; do { echo ${foo} }; done
Loop on lines in a file
for foo in $(cat data_file.txt); do { echo ${foo} }; done
while loop
This is kind of like `watch`:
while sleep 1; do lsof|grep -i Maildir; done
read -- get input from user
In Bash, the builtin command, `read`, is used to get input from a user. It will read input into a variable named REPLY by default or into a given variable name.
read echo $REPLY read YN echo $YN
Remember, `read` is a builtin command, so to get information on using it use `help read`, not `man read`.
Get input directly from a TTY -- not stdin
By default `read` will read input from stdin, but there are situations when you want to get input from the user's TTY instead of stdin. For example, say you piped output from another program into your script then it would try to read input from the user, not the pipeline (what the script now sees as stdin). Another example, you want a boot script to ask the user for input before the console TTY has been opened and attached to stdin (getty) -- this situation came up while I was building an embedded Linux system where I needed to read input from the user through the serial port (/dev/ttyS0) during boot to allow for an optional boot sequence.
In this example, technically `read` still thinks it's reading from stdin -- wWe just redirect input from a tty file.
read YN < `tty`
The `tty` command will tell you which tty you are currently logged into. The console ttys are usually on '/dev/tty[0-9]+' and the virtual ttys used for SSH logins are on '/dev/pts/[0-9]+'.
$ tty /dev/pts/13
If you switch to a console screen (CTRL-ALT-F1 and ALT-F7 to return to X11) and then you login you will see that you now become the owner of /dev/tty1. Switch to a console and login then switch back to X11 (ALT-F7) and from a shell, you see that you now own /dev/tty1. When you logout /dev/tty1 will return to root ownership.
$ ll /dev/tty1 crw------- 1 root root 4, 1 2009-01-04 06:02 /dev/tty1 $ ll /dev/tty1 crw------- 1 my_user tty 4, 1 2009-01-04 06:03 /dev/tty1
get yes/no input from user
YES=1 NO=0 INVALID=-1 yesno() { echo -e $1 VALID_YN=$FALSE YN= rval= echo -e " [y/n] \c" read YN if [ -z "$YN" ] then VALID_YN=$FALSE rval=$INVALID else case "$YN" in [Yy]*) VALID_YN=$TRUE rval=$YES ;; [Nn]*) VALID_YN=$TRUE rval=$NO ;; *) VALID_YN=$FALSE rval=$INVALID ;; esac fi if [ $rval -eq $INVALID ] then echo "Invalid Response..." fi return $rval }
read a single character key then return -- with no Enter required
The following is a discussion of `stty` command. In Bash and Korn shell you can already get a single character using `read`. The following will set the variable, CHARACTER, with a single key read from stdin: `read -r -s -n 1 CHARACTER`.
Using `stty` can get confusing because many different examples do the same things in seemingly different ways. The differences are because the `stty` command has redundant and complimentary ways of doing things. For example, `stty icanon` is the same as `stty -cbreak` and `stty raw` is the same as `stty -cooked`. Raw mode does the same thing and more as '-icanon'.
This reads a single character without echo. It works two ways. If you pass no arguments to `readc` then it will create the variable REPLY and set it to the character read from stdin. If you pass a variable name argument to `readc` then it will set the given variable name to the character read from stdin.
# This reads a single character without echo. # If a variable name argument is given then it is set to a character read from stdin. # else the variable REPLY is set to a character read from stdin. # This is equivalent to `read -r -s -n 1` in Bash. # These two examples read a single character and print it: # readc CHARACTER # echo "CHARACTER is set to ${CHARACTER}." # readc # echo "REPLY is set to ${REPLY}." readc () { previous_stty=$(stty -g) stty raw -echo char=`dd bs=1 count=1 2>/dev/null` stty "${previous_stty}" if [ -n "$1" ] ; then eval $1="${char}" else REPLY="${char}" fi }
check if running as root
if [ $(id -u) -eq 0 ]; then echo "You are root." fi
Or check if not root...
if [ $(id -u) -ne 0 ]; then echo "You must be root to run this." fi
check if process is running
Show the pids of all processes with name "openvpn":
ps -C openvpn -o pid=
Show if a process with pid=12345 is running:
kill -0 12345 echo $?
Check if a process with a given command name and pid is still running. For example, check if ssh process is running with pid 12345: "checkpid ssh 12345". Checkpid script:
#!/bin/sh # example: checkpid ssh 12345 CMD=$1 PID=$2 for QPID in $(ps -C $CMD -o pid=); do if [ $QPID = $PID ]; then echo "running" exit 0 fi done echo "not running" exit 1