You can consider this repository as a bash cheatsheet, I am focusing on the main points and basic syntax of the bash. There are also some urls and resources for deep diving.
- Bash Scripting, intro to bash
- Table of Content
- Shebang
- Variables
- Operators
- Different types of brackets in bash
- Play with quotations: " and '
- If statement
- For loop
- While & Until
- Case statement
- Select statement
- Standard input and output, STDIN, STDOUT, STDERR
- Real world examples
- License
This first line (#!/bin/bash or #!/bin/sh) has a name. It is known as ‘she-bang‘(shabang). This derives from the concatenation of the tokens sharp (#) and bang (!). It is also called as sh-bang, hashbang, poundbang or hash-pling
A quotation from an amazing article by Sanjay Mishra:
Here is another article of articles series by Rayan.
Let's recap:
It looks like a comment line at the file top most. There must not be spaces between #
, !
or the interpreter path, according to Rayan. Also, when you are lazy and you don't put it in your script, the default is set to the current environment of the terminal.
As said in the pre-mentioned article of articles series by Rayan
If you are at a terminal and running the Bash shell and you execute a script without a shebang then Bash will assume it is a Bash script.
Example:
#!/bin/bash
echo "As-salamu alaykum"
This is weird when you know that the shebang line can be included in python and javascript files as an example:
#!/usr/bin/env node
console.log('As-salamu alaykum from javascript!');
You can even run any command or program the will stdout the intrpreter:
#! eval "$(echo '/bin/bash')"
declare
expression: https://www.computerhope.com/unix/bash/declare.htm
$?
: the exit code of the last performed command\$$
: represents process id (pid)$@
: all arguments$#
: arguments count$0
: the program running this script$1
: the first argument, or null$LINENO
: the line number where the $LINENO is used$RANDOM
: random integer in [0, 32767] (signed "+" 16-bit integer).
Useful resources:
- Bash variable expansion: https://www.youtube.com/watch?v=yTijxqjZhRo
- Shell parameter expansion: https://www.gnu.org/software/bash/manual/bash.html#Shell-Parameter-Expansion
var=
var123=(1 2 3 4)
# to print names of the declared variables
echo ${!var@} # var var123
# to get the indices of an array, see
# this used with for loop here below
echo ${!var123[@]} # 0 1 2 3
Resources: basic-operators-in-shell-scripting
You can find most of these operators by this command: info expr
.
-
add:
$num1 + $num2
or$num1 += $num2
-
subtract:
$num1 - $num2
or or$num1 -= $num2
-
multiplication:
$num1 * $num2
or or$num1 *= $num2
-
division:
$num1 / $num2
or or$num1 /= $num2
-
power:
$num1 ** $num2
or or$num1 **= $num2
-
Modulus:
$num1 % $num2
or or$num1 %= $num2
-
Uniary operator, increase:
$num1++
or or++$num1
-
Uniary operator, decrease:
$num1--
or or--$num1
Using
let
to do computations: click here
You can find these operators by this command: info test
.
!($true)
STRING = STRING
STRING != STRING
INTEGER1 -eq INTEGER2
INTEGER1 -gt INTEGER2
INTEGER1 -lt INTEGER2
INTEGER1 = INTEGER2
INTEGER1 \> INTEGER2
INTEGER1 \< INTEGER2
See the amazing answers here: What is the difference between the Bash operators [[ vs [ vs ( vs ((?
What is the difference between the "...", '...', $'...', and $"..." quotes in the shell?
Let keep it simple, I will show you the "if statment" syntax and then explore different commonly used if examples.
if
command-list1
then
command-list2
else
command-list3
fi
We can use a command, chained command with &&
abd ||
, or multiple lines as if you are writing in a separate file until a then
command is found.
If will say the command-list1 is true if the exit code of the whole list is 0, a sign of success, in other words if the last command exits with 0, if it is other than a zero it will be false...
Here are some examples of these command that you will regularly with...
Some arguments that you can use:
Operator Description
! EXPRESSION The EXPRESSION is false.
-n STRING The length of STRING is greater than zero.
-z STRING The lengh of STRING is zero (ie it is empty).
STRING1 = STRING2 STRING1 is equal to STRING2
STRING1 != STRING2 STRING1 is not equal to STRING2
INTEGER1 -eq INTEGER2 INTEGER1 is numerically equal to INTEGER2
INTEGER1 -gt INTEGER2 INTEGER1 is numerically greater than INTEGER2
INTEGER1 -lt INTEGER2 INTEGER1 is numerically less than INTEGER2
-d FILE FILE exists and is a directory.
-e FILE FILE exists.
-r FILE FILE exists and the read permission is granted.
-s FILE FILE exists and it's size is greater than zero (ie. it is not empty).
-w FILE FILE exists and the write permission is granted.
-x FILE FILE exists and the execute permission is granted.
Another important idea to know is that [ condition ]
is the exact same as test condition
, it is just a different way to write the command as standarized by POSIX according, here is the source.
A quote from this answer says:
So when you write
[ [ "$A" -eq "0" ] || [ "$B" -ne "0" ] ] && [ "$C" -eq "0" ]that's parsed as
[ [ "$A" -eq "0" ] || [ "$B" -ne "0" ] ] && [ "$C" -eq "0" ]which is the same as
test [ "$A" -eq "0" || test "$B" -ne "0" ] && test "$C" -eq "0"Notice the unbalanced brackets? Yeah, that's not good. Your attempt with parentheses has the same problem: spurious brackets.
if (( math expressions )); then
echo "do command"
fi
if (PATH=""; true); then
echo "do command"
fi
if [ condition ]; then ...; fi
if [ 1 = 2 ]; then ...; fi
if [ 1 == 2 ]; then ...; fi # invalid syntax
if [1 = 2 ]; then ...; fi # invalid syntax
if [ 1=2 ]; then ...; fi # invalid syntax
if [ condition1 ] && { [ condition2 ] || [ condition3 ]; }; then ...; fi
if [[ 1 -gt 2 ]]; then ...; fi
if [[ 1 > 2 && (... || ... && ...) ]]; then ...; fi
if [[ "$foo" = a* ]]; then ...; fi # checks patterns
See the awesomeness here:
╭─muhammad-alsawalhy@arch.zsh ‹main›
╰─➤ if a
if> b
if> true
if> then
then> echo it is true
then> else
else> echo it is false
else> fi
zsh: command not found: a
zsh: command not found: b
it is true
- Loop over ranges of values
for var_name in {start..end}; do; done
### or ###
for var_name in {start..end..increment}; do; done
- For loop can do such as in JavaScript, Java, C#, etc...:
for (( i=1; i<=10; i++ )); do echo $i; done
- An alternative to the previous code:
for i in `seq 10`; do echo $i; done
- Iterate over items of an array, The second method is more precise because if any of the items includes white spaces, this item will be considered multiple items separated by these white spaces defined in this
$IFS
.
for item in ${arr[@]}; do echo $item; done
for index in ${!arr[@]}; do echo ${arr[index]}; done
- You can use
break
statement to break if a specific condition satisfied, orcontinue
to skip the following commands in the loop:
for (( i=0; i<=10; i++ )); do
if [ $i -eq 5 ]; then continue
elif (( i == 7 )); then break fi
echo $i
done
You can you a logical or math expression:
while (condition is true); do
# commands ...
done
The same structure for until
:
until (condition becomes true); do
# commands ...
done
Example:
This is an alternative to if elif elif else fi
, it is similar to switch
in C-like languages, JavaScript, and many more.
case $1 in
first_choice)
# command
;;
second_choice)
# command
;;
*)
# Default logic
echo $"Usage: $0 {start|stop}"
exit 1
;;
esac
To make the script user choose from a menu.
select user_choice in first_choice 2nd_choice 3rd_choice; do
echo $user_choice
done
command='echo text | cat | sed s/text/another\ text/'
eval $command
eval $command > file # redirect STDOUT --to--> ./file
eval $command >&2 # redirect STDOUT --to--> STDERR
eval $command 1>&2 # redirect STDOUT --to--> STDERR
eval $command >&2 > ./file # redirect STDOUT --to--> both STDERR and ./file
eval $command 2> /dev/null # redirect STDERR --to--> /dev/null
eval $command >& /dev/null # redirect both STDOUT and STDERR --to--> /dev/null
Some valuable answers to a question on stackoverflow, How to detect if my shell script is running through a pipe?.
You can put the following code in a file, e.g. ./handling-STDs
, then try something like echo text | ./handling-STDs 1 > ./file
, ./handling-STDs 1 < ./file
, etc...
Click to see all cases, ./handling-STDs
#!/bin/bash
if [ "$1" = '1' ]; then
echo -----------------------------
if [ -t 0 ]
then echo 'no input, it is the teminal'
else echo "there is a source of input"; fi
echo -----------------------------
if [ -t 1 ]
then echo 'STDOUT to the terminal'
else echo "STDOUT *NOT* to the terminal"; fi
echo -----------------------------
if [ -t 2 ]
then echo 'STDERR to the terminal'
else echo "STDERR *NOT* to the terminal"; fi
echo -----------------------------
elif [ "$1" = '2' ]; then
echo -----------------------------
[[ -t 0 ]] && \
echo 'STDIN is attached to TTY'
[[ -p /dev/stdin ]] && \
echo 'STDIN is attached to a pipe'
[[ ! -t 0 && ! -p /dev/stdin ]] && \
echo 'STDIN is attached to a redirection'
echo -----------------------------
[[ -t 1 ]] && \
echo 'STDOUT is attached to TTY'
[[ -p /dev/stdout ]] && \
echo 'STDOUT is attached to a pipe'
[[ ! -t 1 && ! -p /dev/stdout ]] && \
echo 'STDOUT is attached to a redirection'
echo -----------------------------
[[ -t 2 ]] && \
echo 'STDERR is attached to TTY'
[[ -p /dev/stderr ]] && \
echo 'STDERR is attached to a pipe'
[[ ! -t 2 && ! -p /dev/stderr ]] && \
echo 'STDERR is attached to a redirection'
echo -----------------------------
fi
For example you can do something like this:
#!/usr/bin/env bash
if [ -t 0 ]; then
>&2 echo error: no input is provided, STDIN channel is attached to the tty, your terminal
exit 1
fi
my_content="$(cat)"
echo "I received some data:"
echo "-----------------------------------"
echo "$my_content"
-
Manipulate a string
-
Manipulate an array
- Take a look at my examples of arrays
- Read lines into array
- Remove last item of an array
- Remove last positional argument
- Remove an item by its value
- How can I join elements of an array in Bash?
- Delete positional arguments (from the argv):
set -- "${@:1:2}" "${@:4:8}" # delete the 3rd argument
argv=("$@"); unset 'argv[2]'; set -- "${argv[@]}"
Check my own scripts that made my life easier
MIT