POSIX
Shells
Current Shell
There is a convention that if the logged in user is
root
, then the shell prompt character is #
,
while non-root users show $
.
To print which shell you're currently using, try one of these options:
$ echo $0
bash
$ echo $SHELL
/bin/bash
$ ps -p $$
PID TTY TIME CMD
6549 pts/4 00:00:00 bash
Cross-Shell Topics
POSIX-compatible shell scripting
POSIX defines a standard for shell scripts.
Variable Expansion
Variable expansion supports the following operations:
- Return the value of a variable or a default value:
${VARIABLE:-DEFAULT}
- Return the value of a variable if non-null or a DEFAULT value if
null:
${VARIABLE:-DEFAULT}
- Return the length of a variable value:
${#VARIABLE}
- Return the value of a variable with the specified suffix removed:
${VARIABLE%SUFFIX}
- Return the value of a variable with the specified prefix removed:
${VARIABLE#SUFFIX}
Create a file in one command
Without variable expansion (single or double quotes around the heredoc word):
cat > diag.sh << 'EOF'
#!/bin/sh
OUTPUTFILE="diag_$(hostname)_$(date +%Y%m%d_%H%M%S).txt"
echo "[$(date)] Started command" >> "${OUTPUTFILE}" 2>&1
uname >> "${OUTPUTFILE}" 2>&1
echo "[$(date)] Finished command" >> "${OUTPUTFILE}" 2>&1
EOF
In most shells, a dash at the end of <<
will strip
leading tabs (but not spaces); for example:
cat > diag.sh <<- 'EOF'
#!/bin/sh
OUTPUTFILE="diag_$(hostname)_$(date +%Y%m%d_%H%M%S).txt"
echo "[$(date)] Started command" >> "${OUTPUTFILE}" 2>&1
uname >> "${OUTPUTFILE}" 2>&1
echo "[$(date)] Finished command" >> "${OUTPUTFILE}" 2>&1
EOF
To use variable expansion, remove the single or double quotes around the heredoc word.
If a wrapper command is needed such as sudo
:
sudo sh -c "cat > diag.sh" << 'EOF'
#!/bin/sh
OUTPUTFILE="diag_$(hostname)_$(date +%Y%m%d_%H%M%S).txt"
echo "[$(date)] Started command" >> "${OUTPUTFILE}" 2>&1
uname >> "${OUTPUTFILE}" 2>&1
echo "[$(date)] Finished command" >> "${OUTPUTFILE}" 2>&1
EOF
test
The test
command is often used in boolean expressions and the short-hand is
[]
:
- Check if a string is empty (
-z
could be used but this is more readable):if [ "${1}" = "" ]; then echo "First argument missing" exit 1 fi
- Check if a string is non-empty (
-n
could be used but this is more readable):if [ "${1}" != "" ]; then echo "First argument missing" exit 1 fi
- Check string equality (note that
==
is not standard but supported by many modern shells):if [ "${1}" = "somestring" ]; then echo "First argument is somestring" exit 1 fi
- Check if file exists:
if [ -f "${1}" ]; then echo "File exists and is a regular file" exit 1 fi
- Check if file doesn't exist:
if ! [ -f "${1}" ]; then echo "File does not exist" exit 1 fi
- Check if directory exists:
if [ -d "${1}" ]; then echo "Directory exists" exit 1 fi
Globs
When using a glob (*
) in a terminal to expand to a list
of files and/or directories, by default, files beginning with a dot
(.
), also known as dotfiles or hidden files, are not
included in the list. To include these, first disable this behavior by
running:
set
set is a command to configure options for the current context. One or more flags may be specified at the same time.
set -e
exits the script if a command receives an error.set -x
prints each command tostderr
before it is run.
It is common to put set -e
at the top of a script to
exit if an error is observed:
#!/bin/sh
set -e
case
The case
structured is describe in Case
Conditional Construct. Each case supports wildcards. Example:
case "${somevariable}" in
ta0*)
echo "First branch";
;;
itc*)
echo "Second branch";
;;
*)
echo "Default branch";
;;
esac
POSIX-compliant one-liners
- Generate random character sequence of a-z, A-Z, or 0-9 (replace
-w 22
with the count):cat /dev/random | LC_ALL=C tr -dc 'a-zA-Z0-9' | fold -w 22 | head -n 1
- Loop through a list of numbers (replace
MAX=10
with the maximum; exclusive):i=1; MAX=11; while [ "$i" -ne $MAX ]; do echo $i; i=$(( $i + 1 )); done
Script template
#!/bin/sh
# Comment about this script
usage() {
printf "Usage: %s [-v] [-d DELAY] COMMAND [ARGUMENTS...]\n" "$(basename "${0}")"
cat <<"EOF"
-d: DELAY between executions in seconds.
-v: verbose output to stderr
EOF
exit 22
}
DELAY="30"
VERBOSE=0
OPTIND=1
while getopts "d:hv?" opt; do
case "$opt" in
d)
DELAY="${OPTARG}"
;;
h|\?)
usage
;;
v)
VERBOSE=1
;;
esac
done
shift $((OPTIND-1))
if [ "${1:-}" = "--" ]; then
shift
fi
if [ "${#}" -eq 0 ]; then
echo "ERROR: Missing COMMAND"
usage
fi
printInfo() {
echo "[$(date '+%Y-%m-%d %H:%M:%S.%N %Z')] $(basename "${0}"): ${@}"
}
printVerbose() {
echo "[$(date '+%Y-%m-%d %H:%M:%S.%N %Z')] $(basename "${0}"): ${@}" >> /dev/stderr
}
printInfo "started with delay ${DELAY}"
[ "${VERBOSE}" -eq "1" ] && printVerbose "Commands: ${@}"
"$@"
printInfo "finished"
bash
Change the command prompt to include more information:
$ export PS1="[\u@\t \w]\$ "
Search command history using Ctrl+R and typing and recall with ESC or execute with ENTER:
$ less /etc/hosts
$ othercommand1
$ othercommand2
$ # Now type Ctrl+R and type less
(reverse-i-search)`less': less /etc/hosts
Recall and execute the last command with !!:
$ /opt/IHS/bin/apachectl restart
$ sudo !!
Recall and execute the first previous command containing a search string with !?:
$ /opt/IHS/bin/apachectl restart
$ !?apachectl
Recall and execute the last command but replace all instances of one expression with another:
$ !!:gs/stop/start/
Use the last argument of the previous command with !!:$, or all arguments of the previous command with !!:*
Search history and re-run a particular command based on its position in history:
$ history | grep startServer.sh
527 /opt/IBM/WebSphere/profiles/node01/bin/startServer.sh server1
543 /opt/IBM/WebSphere/profiles/node01/bin/startServer.sh server2
$ !543
Print the name of the current user:
whoami
Extract all .tar.gz files into subdirectories using the name of the file without the extension:
for i in *.tar.gz; do mkdir `basename $i .tar.gz` && mv $i `basename $i .tar.gz` && pushd `basename $i .tar.gz` && tar xzvf $i && rm $i && popd; done
Extract all .tar files into subdirectories using the name of the file without the extension:
for i in *.tar; do mkdir `basename $i .tar` && mv $i `basename $i .tar` && pushd `basename $i .tar` && tar xvf $i && rm $i && popd; done
Extract all .zip files into subdirectories using the name of the file without the extension:
for i in *.zip; do mkdir `basename $i .zip` && mv $i `basename $i .zip` && pushd `basename $i .zip` && unzip $i && rm $i && popd; done
Gunzip all .gz files in a directory:
find . -type f -name "*gz" -print | while read line; do pushd `dirname $line`; gunzip `basename $line`; popd; done
Change to a directory by replacing a part of the current directory:
cd ${PWD/Dmgr01/AppSrv01}
Recall the last word of the previous command using Alt+.
Command line navigation
^
refers to the Ctrl
key. The
⎇
refers to the Alt
key.
Move cursor:
- Beginning:
^a
or^xx
- Beginning of word:
⎇b
- End:
^e
- End of word:
⎇f
- First occurrence of character
X
to the right:^]X
- First occurrence of character
X
to the left:^⎇X
Delete content:
- Delete to the beginning:
^u
- Delete to the end:
^k
- Delete a word to the left:
^w
- Delete a word to the right:
⎇d
- Paste what was deleted:
^y
Global aliases
For truly global aliases, update the scripts for both interactive shells (/etc/profile.d/*) and non-interactive shells (/etc/bashrc or /etc/bash.bashrc, depending on the distribution).
First, create a shell script with the commands you want to run and place it in a common location such as /etc/globalprofile.sh:
#!/bin/sh
alias x="exit"
alias l="ls -ltrh"
export PS1="[\u@\t \w]\$ "
Then add the execute permission:
# chmod +x /etc/globalprofile.sh
Finally, append the following line to both interactive and non-interactive shell script locations (e.g. /etc/bashrc):
source /etc/globalprofile.sh
zsh
Completion
Initialize completion by putting the following at the top of
~/.zshrc
:
autoload -U compinit; compinit
When you start a new terminal tab, if you receive the error "zsh
compinit: insecure directories, run compaudit for list.", then run
compaudit
and, for each directory, run:
sudo chown -R $(whoami):staff $DIR
sudo chmod -R 755 $DIR
Script to watch netstat to create file triggers
watchoutbound.sh
:#!/bin/sh SEARCH="10.20.30.100:50000" SOCKETTHRESHOLD="150" SLEEPINTERVAL="30" while true; do currentcount="$(netstat -an | awk '{print $5}' | grep -c "${SEARCH}")" if [ ${currentcount} -ge ${SOCKETTHRESHOLD} ]; then echo "$(date) Threshold exceeded with ${currentcount} outbound connections to ${SEARCH}" >> /tmp/trigger.txt fi sleep ${SLEEPINTERVAL} done
chmod +x watchoutbound.sh
nohup ./watchoutbound.sh &
awk
This section has been moved to awk.
Truncating Logs
While some operating systems have commands specifically for truncation (e.g. "truncate" on Linux), it is simpler and more cross-platform to simply write /dev/null on top of a file to truncate it:
cat /dev/null > file.log
This does not work with sudo because the redirection operator occurs outside of the sudo. In that case, you can use tee:
cat /dev/null | sudo tee file.log
Defunct Processes
Use ps -elf | grep defunct
to monitor defunct
processes.
"Processes marked <defunct> are dead processes (so-called "zombies") that remain because their parent has not destroyed them properly." (http://man7.org/linux/man-pages/man1/ps.1.html)
"A defunct process, also known as a zombie, is simply a process that is no longer running, but remains in the process table to allow the parent to collect its exit status information before removing it from the process table. Because a zombie is no longer running, it does not use any system resources such as CPU or disk, and it only uses a small amount of memory for storing the exit status and other process related information in the slot where it resides in the process table." (http://www-01.ibm.com/support/docview.wss?uid=isg3T1010692)
Defunct processes are processes that have exited and are waiting for the parent process to read its exit code. Most of the resources of the exited process are released; however, the PID, exit code, and process table entries are still resident and a persistent and large number of defunct processes can limit scalability. Every process will be defunct, but normally it is only for a short period of time. Normally, persistent defunct processes mean that the parent process is hung. In the case of WAS, this is usually the nodeagent process. To remove defunct processes, kill the parent process. Before doing this, gather diagnostics on the parent process such as performing activity on it to see if it is still alive, requesting a thread dump, and finally requesting a core dump. Killing the parent process will cause the parent process of the defunct process to become the init (1) process which will then read the exit code and allow the defunct process to finish.
Example C Program that Crashes
test.c
:
#include <stdio.h>
int main(int argc, char** argv) {
char *p = 0;
printf("Hello World\n");
printf("p: %d\n", *p);
return 0;
}
Run:
$ gcc -g test.c
$ ./a.out
SSH
To bypass any configured private keys:
$ ssh -o PubkeyAuthentication=no user@host
SSH Port Forwarding
Often you can SSH into a box but other ports that you want to access are blocked by firewalls. You can create an SSH tunnel that takes traffic on your machine at some port, forwards it through an SSH connection and sends it to a different port on the target server. For example, let's say hostX has something that is listening on port 8879 which you can't access from outside that box. You can create a tunnel like this:
$ ssh -L 9999:hostX:8879 sshuser@hostX
Now you should have a listening port on localhost port 9999. You can access this port through your client program as you would access port 8879 on hostX.
This can also be done with programs such as putty by using the tunnel option:
kill
kill is used to send signals to processes. The general format of the command is:
$ kill -${SIGNAL} ${PID}
${SIGNAL} is either a number or the name of the signal.
For example, to send the equivalent of Ctrl+C to a process 123:
$ kill -INT 123
less
less is a common command to browse files and input:
$ less input
Tips:
- Jump to the beginning of input:
g
- Jump to the end of input:
G
- Jump to a line number N:
Ng
- Go to the next input:
:n
- Search for something:
/SEARCH
- Find next:
n
- If you jump to the end of input and it says "Calculating line numbers," press Ctrl+C if you don't need to do this to stop the calculation.
- Start tailing a log:
Shift+F
- Enable any command line option after the file has been loaded:
-OPTION
} Enter. Common ones:- Show line numbers:
-N
- Show percent progress in file:
-m
- If it shows
byte X
then less doesn't know how many bytes are available in total so it can't calculate a perecentage. Go to the end and then the beginning:G
}g
- If it shows
- Show file name:
-M
- Don't wrap lines:
-S
- Show line numbers:
tail
tail may be used to skip the first N lines of input using
-n (N+1)
. For example, to skip the first line of input:
$ tail -n +2 input
sort
Sort by a particular column using -k:
$ sort -k 3 input
Sort numerically:
$ sort -k 3 -n input
bc
paste and bc may be used to sum a set of numbers from input:
$ cat input | paste -sd+ | bc
sed
sed and bc may be used to do simple math on input:
$ cat input | sed 's/$/*1024/' | bc
- Print lines 4-10:
sed -n '4,10p'
- Delete first 5 lines:
sed '1,5d'
- Delete blank lines:
sed '/^$/d'
Perl
perl is a commonly used scripting language. A perl script normally has the .pl extension and starts with this shebang line:
#!/usr/bin/env perl
Useful command line options:
- perldoc perlrun: Man page of executing the perl interpreter.
- -e: Specify perl code on the command line rather than a perl file.
- -p: Run specified perl command on each line of standard input.
- -n: Same as -p except that each line is not also printed.
For example, to convert a POSIX date epoch into a human-readable time:
$ date +%s | perl -ne 's/(\d+)/localtime($1)/e;'
The $_ variable will contain each line of the file.
Code may be run at the start and end using BEGIN {} and END {} blocks, respectively:
$ date +%s | perl -ne 'BEGIN { print "Starting\n"; } s/(\d+)/localtime($1)/e; END { print "Finished\n"; }'
Useful things to remember in perl:
- $x =\~ /$REGEX/: Return true if $REGEX matches $x
- $x =\~ s/$REGEX//g: Replace all occurrences of $REGEX in $x with nothing.
Commonly used regular expression tokens:
- Match zero or more: *
- Match one or more: +
- Any character: .
- White space character (space, tab, or newline): \s
- Opposite of white space character: \S
- Word character (a-z, A-Z, 0-9, or underscore): \w
- Non-word character: \W
- Digit character (0-9): \d
- Non-digit character: \D
wget
wget may be used to execute an HTTP request:
$ wget http://ibm.com/
Saving to: "index.html"
When multiple URLs are passed to wget, if possible, wget will attempt to re-use the same TCP socket. Use Perl to automate generating the same URL many times on the command line. In the following example, 64 requests will be attempted over the same socket:
$ wget -O/dev/null `perl -e 'print "http://ibm.com/ " x 64;'`
To review response headers and (short) bodies, a useful one-liner is:
$ wget -qS http://ibm.com/ -O-
netcat (nc) / openssl s_client
When you want more control over what goes into the HTTP/HTTPS request, you can use printf and netcat or openssl s_client:
$ printf "GET / HTTP/1.1\r\nHost: example.com\r\n\r\n" | nc 0 80
$ printf "GET / HTTP/1.1\r\nHost: example.com\r\n\r\n" | openssl s_client -connect 0:443 -ign_eof
openssl
Show a remote TLS certificate (replace both instances of
localhost
):
echo | openssl s_client -showcerts -servername localhost -connect localhost:8880 2>/dev/null | openssl x509 -inform pem -noout -text
find
The /usr/bin/find command searches for files recursively based on their name or metadata. Check the bottom of the Linux manual for examples.
$ find /opt/IBM/WebSphere -name server.xml
$ find /opt/IBM/WebSphere -size +100M (note: the M suffix is not portable)
$ find . -name server.xml|grep -vi Templates|xargs grep startupTraceSpecification
gpg
File Encryption
Encrypt a file for storage or transit:
$ gpg --s2k-mode 3 --s2k-count 65536 --force-mdc --cipher-algo AES256 --s2k-digest-algo sha512 -o ${OUTPUTFILE}.pgp --symmetric ${INPUTFILE}
File Decryption
Decrypt a PGP-encrypted file:
$ gpg --output ${OUTPUTFILE} --decrypt ${INPUTFILE}.pgp
touch
changes the timestamp of a file (=access/modification time) to the current date and time:
$ touch input
Tip: "touch [non-existing filename]" will create a new empty file (no directories). You can also create several new files using "touch [newfile1 newfile2 newfile3]"
Filenames and special characters
Special characters in the terminal include $ <> & |;"'\
If you'd like to use them, you'll need to precede "escape" them with a \ [back slash]. There is no way you can create a filename with a / [forward slash] or null character.
Auto completion
Use "cd + [first few letters of your filename] + TAB {+ TAB}" to change directory to files starting with the letters you specified using auto completion:
$ cd + inp + TAB {or TAB + TAB to display all options}
"ls + TAB + TAB" will open up a list of suggestions which you can use with a given command.
Keyboard shortcuts:
Tips:
- Move to the beginning of a line: CTRL+A
- Move to the end of a line: CTRL+E
- Move one word backwards at a time: ALT+B
- Delete character at cursor location: CTRL+D
- Cut text from the cursor location to the end of the line (beginning of the line): CTRL+K (U) and use CTRL+Y to paste it back; "kill" text and "yank" it.
- Convert the current word [at the beginning of a word] to lowercase (uppercase): ALT+L (U)
tac
Show contents of filename1 and filename2 in a reverse order:
$ tac [filename1][filename2]
nl
Add line numbers to input lines.
Types of commands
To list all available built-in shell commands for a particular shell use:
$ compgen -b
To display your command type you can use "type [command name] OR file [absolute path to command]":
$ type cd
OR type ifconfig and file /sbin/ifconfig
Alternatively, you can use the "which" command to display an executable location/the absolute command path (not working for shell built-ins):
$ which ifconfig
To check if a command name is already in use or may be used as an alias you may use "type [potential alias name]":
$ type myalias
Combining commands
- To combine 1stcommand and 2ndcommand etc., use "1stcommand;
2ndcommand;...." Correct commands will be executed unless you use exit:
$ date; cal
- Alternatively, you can use "&&" to combine commands.
However, this will only execute until it encounters an error ("short
circuit evaluation"):
$ date && cal
Wildcards
- Represents or matches any characters: *
- Represents or matches a single character: ?
- Match a range of characters: [range of characters]
- Not match a range of characters: [!range of characters]
- Any number from numbers of the digit range:[digit-digit]*
- Uppercase: [[:upper:]]
- Lowercase: [[:lower:]]
- Digit: [[:digit:]]
- Alphabetical: [[:alpha:]]
- Alphanumeric: [[:alnum:]]
- NB: To negate this use [![....:]]
htpasswd
- Create 10 users with random passwords:
i=1; MAX=11; OUTPUT=users.htpasswd; while [ "$i" -ne $MAX ]; do PASSWORD="$(cat /dev/random | LC_ALL=C tr -dc 'a-zA-Z0-9' | fold -w 22 | head -n 1)"; htpasswd $(if ! [ -f "${OUTPUT}" ]; then printf "%s" "-c"; fi) -B -b "${OUTPUT}" "user${i}" "${PASSWORD}"; echo "user${i} = ${PASSWORD}"; i=$(( $i + 1 )); done
Makefiles
make
uses a Makefile
to automate tasks.
Makefile Basics
A Makefile
is a set of rules of the form:
$TARGET: $PREREQUISITES
$TAB$COMMAND
$TAB$COMMAND
[...]
Each $COMMAND
is run in its own shell.
For example:
a.out: test.c
gcc test.c
Make will only run a rule if the last modification time of
$TARGET
is older than the last modification times of its
$PREREQUISITES
. A target without
$PREREQUISITES
always evaluates to needing to be executed;
for example:
clean:
rm a.out
Comments begin with #
.
The first $TARGET
is the default target when running
make
. Otherwise, specify the targets with
make $TARGET...
; for example, make a.out
.
To ensure POSIX-compliance, the first non-comment line should be the following:
.POSIX:
Makefiles may use macros of the form $(MACRO)
and these
may be nested. Default values may be specified with assignment; for
example:
CFLAGS=-g
Macros may be overridden with environment variables in the calling
shell or on the command line with MACRO=value
; for example,
make CFLAGS="-g -O"
. The $<
macro expands
to the prerequisite.
Makefile Example
.POSIX:
CC=gcc
CFLAGS=-W -O -g -fno-omit-frame-pointer
PREFIX=/usr/local
all: a.out
install: a.out
mkdir -p $(DESTDIR)$(PREFIX)/bin
cp -f a.out $(DESTDIR)$(PREFIX)/bin
a.out: test.c
$(CC) $(LDFLAGS) $<
clean:
rm -f a.out
PHONY
The special .PHONY
target specifies a list of targets which will be executed, if requested,
even
if a file name matching the target exists. For example:
When one directory contains multiple programs, it is most convenient to describe all of the programs in one makefile ./Makefile. Since the target remade by default will be the first one in the makefile, it is common to make this a phony target named ‘all’ and give it, as prerequisites, all the individual programs. For example:
all : prog1 prog2 prog3 .PHONY : all prog1 : prog1.o utils.o cc -o prog1 prog1.o utils.o prog2 : prog2.o cc -o prog2 prog2.o prog3 : prog3.o sort.o utils.o cc -o prog3 prog3.o sort.o utils.o