Bash Cheatsheet
References
- tldp.org
- gnu.org
- the same on devdocs.io: bash (“DevDocs combines multiple API documentations in a fast, organized, and searchable interface.”, devdocs.io)
- mywiki.wooledge.org
Profiling
Check the Execution Time
time COMMAND
- e.g.
time source ./.bashrc
Give verbose output with timestamps and linenumbers
From https://stackoverflow.com/a/4338046:
With modifying the script:
Add
#!/bin/bash -x
# Note the -x flag above, it is required for this to work
PS4='+ $(date "+%s.%N ($LINENO) ")'
at the beginning of your script.
Without modifying the script:
PS4='+ $(date "+%s.%N ($LINENO) ")' bash -x .bashrc
or:
PS4='+ $EPOCHREALTIME ($LINENO) ' bash -x .bashrc
Note: PS4
is a special variable used by set -x
to prefix tracing output. (more)
Step through Bash scripts
From stackexchange:
It’s not exactly profiling, but you can trace your script as it runs.
- Put
set -xv
before the section you want to trace andset +xv
after the section. set -x
enables xtrace, which will show every line that executes.set -v
enables verbose mode, which will also show lines that may have an effect, but are not executed, such as variable assignment.
How to call one shell script from another shell script?
Three ways:
- Make the other script executable with
chmod a+x /path/to/file
, add the#!/bin/bash
line (called shebang) at the top, and the path where the file is to the$PATH
environment variable. Then you can call it as a normal command; - Or call it with the source command (which is an alias for
.
), like this:source /path/to/script
- Or use the bash command to execute it, like:
/bin/bash /path/to/script
The first and third approaches execute the script as another process, so variables and functions in the other script will not be accessible.
The second approach 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). It will of course run all the commands in the other script, not only set variables.
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.
Source instead of Execute
Assuming that you are running bash, put the following code near the start of the script that you want to be sourced but not executed:
if [ "${BASH_SOURCE[0]}" -ef "$0" ]
then
echo "Hey, you should source this script, not execute it!"
exit 1
fi
Exit on Error
set -e
Bad practice: Only use set -e
for debugging purposes.
It’s recommended to use:
trap 'do_something' ERR
to run do_something
function when errors occur. (see below in section “bash traps”)
#!/bin/bash
set -e
# Any subsequent(*) commands which fail will cause the shell script to exit immediately
You can also disable this behavior with set +e
.
Warning: When you use set -e
in a script that you want to be sourced, you must put a set +e
at the end of this script as well as at each point in the script at which the script might exit. Otherwise, set -e
will continue to be set in the shell in which the script was sourced. This will cause the shell to exit whenever one of the following commands entered in this shell fails (and also when tab autocompletion cannot find a matching completion!).
history:
set -e
was an attempt to add “automatic error detection” to the shell. Its goal was to cause the shell to abort any time an error occurred, so you don’t have to put || exit 1 after each important command. This does not work well in practice. bash FAQ
- related:
bash traps
Warning: Do not forget to remove traps after setting them.
command | description |
---|---|
trap 'do_something' ERR |
set a trap that runs do_something on signal ERR ; “the SIG prefix is optional” (from trap --help ), ie. you can write eg. SIGINT or INT |
trap - [signal] |
remove a trap, stackoverflow |
trap -l |
print a list of signal names and their corresponding numbers |
ERR |
similar to set -e , stackoverflow |
EXIT |
useful for cleanup operations, see below “examples” → “exit traps”, exit traps |
- related:
Examples
Example 1: report the line number where an error occurred:
FILENAME=$0
# Colors
RED=$(tput setaf 1)
NC=$(tput sgr0) # No Color
err_report() {
printf "%s%s: Error on line %s.%s\n" "${RED}" "${FILENAME}" "$1" "${NC}"
}
trap 'err_report $LINENO' ERR
# do some work
trap - ERR
Example 2: “exit traps” to perform cleanup operations:
#!/bin/bash
function finish {
# Your cleanup code here
}
trap finish EXIT
Example 3: only exit at the end of a loop: stackoverflow
- important (related to Example 3): behaviour when pressing ctrl + c
- observed in
scripts/dl/cyberdrop-status.sh
- A SIGINT (ctrl + c) is sent to the whole process group (see section “Process Group ID, Process Group Leader”), ie. to all processes that have the same process group ID (PGID).
- So, eg. when there is a
sleep
inside a while loop in your bash script, thesleep
process will receive a SIGINT and the script process will also receive a SIGINT. - If this bash script has a SIGINT
trap
that does not exit the script on SIGINT, then the script will continue running, whereas thesleep
process will terminate immediately (while returning the remaining time). - This means that the next statement in the script after the
sleep
command is executed. Ifsleep
is the last command in the loop, then the next iteration of the loop will start.
- So, eg. when there is a
- observed in
Process Group ID, Process Group Leader
I understand that when a process is started from a shell in linux or unix, a new process group is created with that process as the process leader, giving its PID equal to a new PGID which is used for any processes spawn from that process.
As I understand it, in the case where the process leader is terminated, a new process becomes leader. This new leader would then have a PID inequal to the PGID of the process group.
How to get the PID and corresponding PGID of each process:
Simplest way to determine the process group ID is to use ps
:
ps ax -O tpgid
The second column will be the process group ID.
phth: here you can run eg.
# get the PID and its corresponding PGID
ps ax -O tpgid | grep some_PID
ps ax -O tpgid | grep some_PGID
ps ax -O tpgid | grep name_of_some_process
to get your desired information.
The output will look eg. like this:
$ ps ax -O tpgid | grep some_PID
3521072 3669289 S pts/18 00:00:00 bash
3669289 3669289 S pts/18 00:00:00 /bin/bash ./dl/someprocess.sh
3669347 3669289 S pts/18 00:00:00 sleep 10
3669396 3669395 S pts/23 00:00:00 grep --color=auto 3669289
How to get the PGID of the current process in bash:
ps -o pgid= $$
- related:
Exit after some Time
SECONDS=0
while (( SECONDS < 60)); do
echo "keep running"
sleep 5
done
Shebang
beste Erklärung: askubuntu discussion
oder aus Wikipedia:
In computing, a shebang is the character sequence consisting of the characters number sign and exclamation mark (#!
) at the beginning of a script.
When a text file with a shebang is used as if it is an executable in a Unix-like operating system, the program loader mechanism parses the rest of the file’s initial line as an interpreter directive. The loader executes the specified interpreter program, passing to it as an argument the path that was initially used when attempting to run the script, so that the program may use the file as input data. For example, if a script is named with the path path/to/script
, and it starts with the following line, #!/bin/sh
, then the program loader is instructed to run the program /bin/sh
, passing path/to/script
as the first argument. In Linux, this behavior is the result of both kernel and user-space code.
The shebang line is usually ignored by the interpreter, because the “#
” character is a comment marker in many scripting languages; some language interpreters that do not use the hash mark to begin comments still may ignore the shebang line in recognition of its purpose.
Core
Command Substitution
Syntax: $(command substitution)
Nested Variables:
DIRNAME="$(dirname "$FILE")"
No-op Command
Colon: :
from bash manual:
- “Do nothing beyond expanding arguments and performing redirections. The return status is zero.”
Double-Ampersand vs Semicolon
- semicolon: run the command no matter what the exit status of the previous command is
- double-ampersand: only run the command if the previous command is a success
$ false ; echo "OK"
OK
$ true ; echo "OK"
OK
$ false && echo "OK"
$ true && echo "OK"
OK
$ false || echo "OK"
OK
$ true || echo "OK"
$
Variables
Best Practices
- double quote variables:
echo "$somevar"
instead ofecho $somevar
- “Quoting variables prevents word splitting and glob expansion, and prevents the script from breaking when input contains spaces, line feeds, glob characters and such.”, shellcheck.net
Naming Rules
- numbers in variable names:
- variable names must not start with a number
- variable names can contain numbers
- more precisely: reddit.com
Naming Conventions
Environment variables or shell variables introduced by the operating system, shell startup scripts, or the shell itself, etc., are usually all in CAPITALS1
.
To prevent your variables from conflicting with these variables, it is a good practice to use lower_case
variable names.
export
vs setting a variable
see baeldung.com
$ MYVAR=1729
$ export MYVAR=1729
The first definition creates a variable named MYVAR and assigns it the value 1729. This is a shell variable.
The second definition with the export command is another way of defining a variable. It creates a variable named MYVAR, assigns it the value 1729, and marks it for export to all child processes created from that shell. This is an environment variable.
The main difference between these two is that the export
command makes the variable available to all the subsequent commands executed in that shell. This command does that by setting the export attribute for the shell variable MYVAR
. The export attribute marks MYVAR for automatic export to the environment of the child processes created by the subsequent commands:
$ export MYVAR=1729
$ echo $MYVAR
1729
$ bash # Open a new child shell
$ echo $MYVAR
1729
Note: We can access bash environment variables only one way; the parent shell exports its variables to the child shell’s environment, but the child shell can’t export variables back to the parent shell.
Variable Expansion
“Parameter expansion” (A.K.A “Variable expansion”)
Check if a Variable is Set
if [[ -n "${var+x}" ]]; then
# do something
fi
If
if [ conditions ]; then
# Things
elif [ other_conditions ]; then
# Other things
else
# In case none of the above occurs
fi
Note: The spaces are needed around the brackets. Otherwise, it won’t work. This is because [
itself is a command.
Double Square-Brackets vs Single Square-Brackets
- “If you’re writing a
#!/bin/bash
script then I recommend using[[
instead. The double square-brackets[[...]]
form has more features, a more natural syntax, and fewer gotchas that will trip you up.”, stackoverflow - ”
[[
is bash’s improvement to the[
command. It has several enhancements that make it a better choice if you write scripts that target bash.”, stackoverflow
Double Parentheses, Arithmetic Expansion
- “Double parentheses are used for arithmetic operations”
- “and they enable you to omit the dollar signs on integer and array variables and include spaces around operators for readability.”
- POSIX sh (and all shells based on it, including Bash and ksh) uses the
$(( ))
syntax to do arithmetic, using the same syntax as C. - Bash calls this an “Arithmetic Expansion”, and it obeys the same basic rules as all other
$...
substitutions.
Command as condition
You can specify commands as a condition of if
. If the command returns 0
in its exitcode that means that the condition is true
; otherwise false
. source
$ if /bin/true; then echo that is true; fi
that is true
$ if /bin/false; then echo that is true; fi
$
Pipe
Pipe on a Condition
How to pipe on a condition in Bash
Examples
Using find
in combination with xargs
: the flags -print0
and -0
are necessary because filenames may contain spaces:
# duplicates.txt: filenames with file sizes
for pattern in $(du -sh searchPatternWith\ \(Spaces\)/* | cut -f2- -d'/' | cut -f1 -d'_'); do count=$(find . -iname *$pattern* -print0 | xargs -0 ls | grep .mp4$ | wc -l); if [[ $count -ge 2 ]]; then find . -iname *$pattern* -print0 | xargs -0 du | grep -v searchPatternWith | tee -a duplicates.txt; fi; done
# duplicates2.txt: only filenames
for pattern in $(du -sh searchPatternWith\ \(Spaces\)/* | cut -f2- -d'/' | cut -f1 -d'_'); do count=$(find . -iname *$pattern* -print0 | xargs -0 ls | grep .mp4$ | wc -l); if [[ $count -ge 2 ]]; then find . -iname *$pattern* -print0 | xargs -0 du | grep -v searchPatternWith | grep -v jpg$ | tee -a duplicates2.txt; fi; done
$#
from: askubuntu post)
Zum Beispiel:
if [[ $# -gt 0 ]]; then
# ...
$#
means: number of arguments (like argc
in C)
$1
, $2
, etc.
- called the Positional Parameters
$1
is the first command-line argument passed to the shell script.- It is a way to access the value of the first parameter supplied when executing the script or function.
- For example,
$0
,$1
,$3
,$4
and so on. - If you run
./script.sh filename1 dir1
, then:$0
is the name of the script itself (script.sh
)$1
is the first argument (filename1
)$2
is the second argument (dir1
)$9
is the ninth argument${10}
is the tenth argument and must be enclosed in brackets after$9
.${11}
is the eleventh argument.
Passing Arguments to Scripts
#!/bin/bash
############################################################
# Help #
############################################################
Help()
{
# Display Help
echo "Add description of the script functions here."
echo
echo "Syntax: scriptTemplate [-g|h|v|V]"
echo "options:"
echo "g Print the GPL license notification."
echo "h Print this Help."
echo "v Verbose mode."
echo "V Print software version and exit."
echo
}
############################################################
############################################################
# Main program #
############################################################
############################################################
# Set variables
Name="world"
############################################################
# Process the input options. Add options as needed. #
############################################################
# Get the options
while getopts ":hn:" option; do
case $option in
h) # display Help
Help
exit;;
n) # Enter a name
Name=$OPTARG;;
\?) # Invalid option
echo "Error: Invalid option"
exit;;
esac
done
echo "hello $Name!"
optstring
It is just a string, and each character of this string represents an option. If this option requires an argument, you have to follow the option character by :
.
For example, "cdf:g"
accepts the options c
, d
, f
, and g
; f
requires an additional argument.
If the very first character of optstring is a colon, getopts will not produce any diagnostic messages for missing option arguments or invalid options.
$OPTARG
$OPTARG
stores the value of the argument of the option
Example: when you run ./script.sh -p "someArg"
then the $OPTARG
variable in the p)
case in the while getopts
construct contains "someArg"
Comparison Operators
if [[ $# -gt 0 ]]; then
# ...
else
# ...
fi
=
vs==
vs-eq
, stackoverflow=
and==
are for string comparisons-eq
is for numeric comparisons-eq
is in the same family as-lt
,-le
,-gt
,-ge
, and-ne
==
is specific to bash (not present in sh (Bourne shell), …).- Using POSIX
=
is preferred for compatibility. - In bash the two are equivalent, and in sh
=
is the only one that will work.
Logical Operators
And
[command] && [command]
An AND conditional causes the second command to be executed only if the first command ends and exits successfully.
Or
[command] || [command]
An OR conditional causes the second command to be executed only if the first command ends and exits with a failure exit code (any non-zero exit code).
Not
if ! grep -q sysa /etc/passwd ; then
test
There are two syntaxes for using the test
command.
test EXPRESSION
[ EXPRESSION ]
Note that in the case of [
, there is a space at both ends of the EXPRESSION
.
- e.g.
test 1 -eq 2 && echo "true" || echo "false"
→false
- alternatively:
$ [ 1 -eq 2 ] && echo "true" || echo "false"
Check if File exists
FILE=/etc/resolv.conf
if [ -f "$FILE" ]; then
echo "$FILE exists."
fi
printf
Don’t use variables in the printf format string. Use printf "..%s.." "$foo"
.
printf "%sphth: Need to rename some windows to make some shortcuts work:%s\n" "${RED}" "${NC}"
# instead of
printf "${RED}phth: Need to rename some windows to make some shortcuts work:${NC}\n"
Colors
Standard Stream Redirection: stdin
, stdout
and stderr
- see “What is
/dev/null
”
File Descriptor (part of POSIX API)
- see file descriptor
- Each Unix process should have three standard POSIX file descriptors, corresponding to the three standard streams:
stdin
,stdout
andstderr
. - Each file descriptor has a non-negative integer value:
stdin
: 0,stdout
: 1,stderr
: 2.
Redirect stderr
to a text file
asdfadsa 2> error.txt
Redirect stdout
to a text file
- Note: If no file descriptor value is specified, bash will use
stdout
(i.e. file descriptor value1
) by default. echo "Hello World" > log.txt
Redirect to /dev/null
- Send the output to
/dev/null
:command 1> /dev/null
. - Send the error to
/dev/null
:command 2> /dev/null
. - Send both output and error to
/dev/null
:command 2>&1 /dev/null
.
Redirect stderr
AND stdout to /dev/null
grep -r hello /sys/ &> /dev/null
Redirect ALL output to /dev/null
> /dev/null 2>&1
- unix.stackexchange
- In certain situations, the output may not be useful at all. Using redirection, we can dump all the output into the void:
> /dev/null 2>&1
> /dev/null
dumps all thestdout
to/dev/null
2>&1
redirectsstderr
(file descriptor value:2
) tostdout
(file descriptor value:1
)
- phth note:
> /dev/null 2>&1
idea came from~/git/geohot/openpilot/update_requirements.sh
Job control
List running jobs
jobs -l
(also shows thejobID
s)
bg
- let a process run in the background:
bg %[jobID]
- But output (e.g.
stdout
,stderr
, etc) will still be printed in the terminal! Redirect output to/dev/null
to avoid this.
- But output (e.g.
- sends a
SIGCONT
- like
kill -CONT [processid]
- to start a process in the background:
mycommand &
- additionally, to avoid any output of this process in the current terminal window:
command > /dev/null 2>&1 &
- additionally, to avoid any output of this process in the current terminal window:
fg
- brings a process to the foreground:
fg %[jobID]
nohup
- make a process continue running even if the shell session accidentally dies:
nohup firefox &
- makes a process ignore the
SIGHUP
signal sent to the process - example:
nohup gedit &
, now if the terminal diesgedit
will not be closed!
- use case:
- wenn man z.B. per
ssh
auf einem fremden Rechner arbeitet und dort einen langwierigen Prozess starten möchte, die ssh-Verbindung aber während des Prozesses nicht permanent aktiv sein soll, weil man etwa den eigenen Rechner ausschalten möchte
- wenn man z.B. per
Exit codes
Important: Exit codes, in general, depend on the shell used! Different shells have different exit codes!
Bash
Exit code of the last command:
$ echo $?
Check Exit Code and Run Code
# check, if my custom shortcuts for focussing certain windows work
RED=$(tput setaf 1)
NC=$(tput sgr0) # No Color
wmctrl -l | grep -q main-tmux
if ! "$?" > /dev/null 2>&1; then
printf "%sphth: Need to rename some windows to make some shortcuts work:%s\n" "${RED}" "${NC}"
cat scripts/rename-windows.sh
fi
Signal numbers
man bash
:
When a command terminates on a fatal signal whose number is N, Bash uses the value 128+N as the exit status
These signal numbers are given below for each signal.
See also:
- Zombie (→ Operating Systems Notes)
ctrl - c
- sends the signal
SIGINT
(signal number: 2, bash exit code: 130)- note: Exit code 130 can mean either
SIGINT
or_exit(130)
in bash!SIGINT
and_exit(130)
are not the same (more details on stackoverflow).
- note: Exit code 130 can mean either
- like
kill -SIGINT [processPID]
(Note:kill
sends theSIGTERM
(termination) signal by default unless you specify the signal to send.) - can be intercepted by a program so it can clean its self up before exiting, or not exit at all
ctrl - z
- suspends a running process
- sends a
SIGTSTP
- like
kill -TSTP [processid]
- cannot be intercepted by the program
- note: the process will halt, so if you want it to continue running in the background use
bg
!
ctrl - s
- sends a
SIGSTOP
ctrl - q
- sends a
SIGCONT
ctrl - d
- sends a
SIGHUP
(“signal hang up”) to all jobs in the shell’s job list (runjobs
to see the job list)- this includes foreground and background jobs
Formatting
Clear the Terminal
if [[ "$error_count" -eq 0 ]]; then
# clear the terminal
printf "\033c"
fi