Try / Bash in Y minutes
Bash is a name of the unix shell, which was also distributed as the shell for the GNU operating system and as the default shell on most Linux distros. Nearly all examples below can be a part of a shell script or executed directly in the shell.
Introduction · Variables · Control flow · Files and directories · Running processes · Useful commands
✨ This is an open source guide. Feel free to improve it!
Introduction
First line of the script is the shebang which tells the system how to execute the script:
#!/usr/bin/env bash
# As you already figured, comments start with #.
# Shebang is also a comment.
Simple hello world example:
#!/usr/bin/env bash
echo "Hello world!"
Hello world!
Each command starts on a new line, or after a semicolon:
#!/usr/bin/env bash
echo "This is the first command"; echo "This is the second command"
This is the first command
This is the second command
Variables
Declaring a variable looks like this (I'll skip the shebang from now on):
variable="Some string"
But not like this:
variable = "Some string"
variable: command not found
Bash will decide that variable
is a command it must execute and give an error
because it can't be found.
Nor like this:
variable= "Some string"
Some string: command not found
Bash will decide that Some string
is a command it must execute and give an
error because it can't be found. In this case the variable=
part is seen
as a variable assignment valid only for the scope of the Some string
command.
Using the variable:
echo "$variable"
echo '$variable'
Some string
$variable
When you use a variable itself — assign it, export it, or else — you write
its name without $
. If you want to use the variable's value, you should use $
.
Note that '
(single quote) won't expand the variables!
You can write variable without surrounding quotes but it's not recommended.
Parameter expansion ${...}
:
echo "${variable}"
Some string
This is a simple usage of parameter expansion such as two examples above. Parameter expansion gets a value from a variable. It "expands" or prints the value. During the expansion time the value or parameter can be modified. Below are other modifications that add onto this expansion.
String substitution in variables:
echo "${variable/Some/A}"
A string
This will substitute the first occurrence of Some
with A
.
Substring from a variable:
length=7
# This will return only the first 7 characters of the value
echo "${variable:0:length}"
# This will return the last 5 characters (note the space before -5).
# The space before minus is mandatory here.
echo "${variable: -5}"
Some st
tring
String length:
echo "${#variable}"
11
Indirect expansion:
other_variable="variable"
echo ${!other_variable}
Some string
This will expand the value of other_variable
.
The default value for variable:
echo "${foo:-"DefaultValueIfFooIsMissingOrEmpty"}"
DefaultValueIfFooIsMissingOrEmpty
This works for null (foo=
) and empty string (foo=""
); zero (foo=0
) returns 0
.
Note that it only returns default value and doesn't change variable value.
Arrays:
# Declare an array with 6 elements:
array=(one two three four five six)
echo "First element:"
echo "${array[0]}"
echo ""
echo "All elements:"
echo "${array[@]}"
echo ""
echo "Number of elements:"
echo "${#array[@]}"
echo ""
echo "Number of chars in 3rd element:"
echo "${#array[2]}"
echo ""
echo "Two elements starting from 4th:"
echo "${array[@]:3:2}"
First element:
one
All elements:
one two three four five six
Number of elements:
6
Number of chars in 3rd element:
5
Two elements starting from 4th:
four five
Print all elements each of them on new line:
array=(one two three four five six)
for item in "${array[@]}"; do
echo "$item"
done
one
two
three
four
five
six
Built-in variables:
echo "Last program's return value: $?"
echo "Script's PID: $$"
echo "Number of arguments passed to script: $#"
echo "All arguments passed to script: $@"
echo "Script's arguments separated into different variables: $1 $2..."
Last program's return value: 0
Script's PID: 1
Number of arguments passed to script: 0
All arguments passed to script:
Script's arguments separated into different variables: ...
Brace expansion {...}
is used to generate arbitrary strings:
echo {1..10}
echo {a..z}
1 2 3 4 5 6 7 8 9 10
a b c d e f g h i j k l m n o p q r s t u v w x y z
This will output the range from the start value to the end value.
Note that you can't use variables here:
from=1
to=10
echo {$from..$to}
{1..10}
Expressions are denoted with the following format:
echo $(( 10 + 5 ))
15
Control flow
We have the usual if
structure.
Condition is true if the value of $name
is not equal to the current user's login username:
name="Alice"
if [[ "$name" != "$USER" ]]; then
echo "Your name isn't your username"
else
echo "Your name is your username"
fi
Your name isn't your username
To use &&
and ||
with if
statements, you need multiple pairs of square brackets:
name="Daniya"
age=15
if [[ "$name" == "Steve" ]] && [[ "$age" -eq 15 ]]; then
echo "This will run if $name is Steve AND $age is 15."
fi
if [[ "$name" == "Daniya" ]] || [[ "$name" == "Zach" ]]; then
echo "This will run if $name is Daniya OR Zach."
fi
This will run if Daniya is Daniya OR Zach.
There are other comparison operators for numbers listed below:
-ne
- not equal-lt
- less than-gt
- greater than-le
- less than or equal to-ge
- greater than or equal to
There is also the =~
operator, which tests a string against the Regex pattern:
email=me@example.com
if [[ "$email" =~ [a-z]+@[a-z]{2,}\.(com|net|org) ]]
then
echo "Valid email!"
fi
Valid email!
There is also conditional execution:
echo "Always executed" || echo "Only executed if first command fails"
Always executed
echo "Always executed" && echo "Only executed if first command does NOT fail"
Always executed
Only executed if first command does NOT fail
Bash uses a case
statement that works similarly to switch in Java and C++:
variable=1
case "$variable" in
# List patterns for the conditions you want to meet
0) echo "There is a zero.";;
1) echo "There is a one.";;
*) echo "It is not null.";; # match everything
esac
There is a one.
for
loops iterate for as many arguments given.
The contents of $variable
is printed three times:
for variable in {1..3}
do
echo "$variable"
done
1
2
3
Or write it the "traditional for loop" way:
for ((a=1; a <= 3; a++))
do
echo $a
done
1
2
3
They can also be used to act on files.
This will run the command cat
(prints file contents) on file1
and file2
:
for variable in file1.txt file2.txt
do
cat "$variable"
done
hello from file1
hello from file2
...or the output from a command.
This will cat
the output from ls
(lists files that match the pattern).
for output in $(ls *.txt)
do
cat "$output"
done
hello from file1
hello from file2
Bash can also accept patterns, like this to cat
all the text files in current directory:
for output in ./*.txt
do
cat "$output"
done
hello from file1
hello from file2
While loop:
while [ true ]
do
echo "loop body here..."
break
done
loop body here...
You can also define functions:
function foo ()
{
echo "All arguments passed to function: $@"
echo "Arguments separated into different variables: $1 $2..."
echo "This is a function"
returnValue=0 # Variable values can be returned
return $returnValue
}
Call the function foo
with two arguments, arg1
and arg2
:
foo arg1 arg2
All arguments passed to function: arg1 arg2
Arguments separated into different variables: arg1 arg2...
This is a function
Return values can be obtained with $?
:
foo > /dev/null # hide the output
resultValue=$?
echo "result = $resultValue"
result = 0
More than 9 arguments are also possible by using braces, e.g. ${10}
, ${11}
, etc.
You can also define functions like this:
bar ()
{
echo "Another way to declare functions!"
return 0
}
# call the function `bar` with no arguments:
bar
Another way to declare functions!
Files and directories
Our current directory is available through the command pwd
.
pwd
stands for "print working directory".
We can also use the built-in variable $PWD
.
Observe that the following are equivalent:
# execs `pwd` and interpolates output
echo "I'm in $(pwd)"
# interpolates the variable
echo "I'm in $PWD"
I'm in /sandbox
I'm in /sandbox
If you get too much output in your terminal, or from a script, the command
clear
clears your screen:
clear
Ctrl-L
also works for clearing output.
Reading a value from input:
echo "What's your name?"
read name
# Note that we didn't need to declare a new variable.
echo "Hello, $name!"
Unlike other programming languages, bash is a shell so it works in the context
of a current directory. You can list files and directories in the current
directory with the ls
command:
ls
main.sh
This command has options that control its execution:
# Lists every file and directory on a separate line
ls -l
echo ""
# Sorts the directory contents by last-modified date (descending)
ls -t
echo ""
# Recursively `ls` this directory and all of its subdirectories
ls -R
total 8
-r--r--r-- 1 sandbox sandbox 40 Feb 12 00:54 file.txt
-r--r--r-- 1 sandbox sandbox 226 Feb 12 00:54 main.sh
file.txt
main.sh
.:
file.txt
main.sh
Results (stdout) of the previous command can be passed as input (stdin) to the next command
using a pipe |
. Commands chained in this way are called a "pipeline", and are run concurrently.
The grep
command filters the input with provided patterns.
That's how we can list .txt
files in the current directory:
ls -l | grep "\.txt"
-r--r--r-- 1 sandbox sandbox 40 Feb 12 00:53 file.txt
Use cat
to print files to stdout:
cat file.txt
Hello, World!
Today is a beautiful day.
We can also read the file using cat
:
Contents=$(cat file.txt)
# "\n" prints a new line character
# "-e" to interpret the newline escape characters as escape characters
echo -e "START OF FILE\n$Contents\nEND OF FILE"
START OF FILE
Hello, World!
Today is a beautiful day.
END OF FILE
Use cp
to copy files or directories from one place to another.
cp
creates NEW versions of the sources,
so editing the copy won't affect the original (and vice versa).
Note that it will overwrite the destination if it already exists.
cp file.txt /tmp/clone.txt
cp -r /sandbox/ /tmp # recursively copy
ls -lR /tmp
/tmp:
total 4
-r--r--r-- 1 sandbox sandbox 40 Feb 12 01:00 clone.txt
drwxr-xr-x 2 sandbox sandbox 80 Feb 12 01:00 sandbox
/tmp/sandbox:
total 8
-r--r--r-- 1 sandbox sandbox 40 Feb 12 01:00 file.txt
-r--r--r-- 1 sandbox sandbox 80 Feb 12 01:00 main.sh
Look into scp
or sftp
if you plan on exchanging files between computers.
scp
behaves very similarly to cp
.
sftp
is more interactive.
Use mv
to move files or directories from one place to another.
mv
is similar to cp
, but it deletes the source.
mv
is also useful for renaming files!
touch /tmp/src.txt
mv /tmp/src.txt /tmp/dst.txt
ls /tmp
dst.txt
Since bash works in the context of a current directory, you might want to run your command in some other directory. We have cd for changing location:
cd ~ # change to home directory
cd # also goes to home directory
cd .. # go up one directory
# (^^say, from /home/username/Downloads to /home/username)
# change to specified directory
cd /sandbox
# change to another directory
cd /var/log/..
# change to last directory
cd -
/sandbox
Use subshells to work across directories:
(echo "First, I'm here: $PWD") && (cd /tmp; echo "Then, I'm here: $PWD")
pwd # still in first directory
First, I'm here: /sandbox
Then, I'm here: /tmp
/sandbox
Use mkdir
to create new directories:
mkdir /tmp/one
# the `-p` flag causes new intermediate directories to be created as necessary.
mkdir -p /tmp/two/three/four
# show directory tree
tree /tmp
/tmp
├── one
└── two
└── three
└── four
4 directories, 0 files
If the intermediate directories didn't already exist, running the above
command without the -p
flag would return an error.
You can redirect command input and output (stdin, stdout, and stderr) using "redirection operators". Unlike a pipe, which passes output to a command, a redirection operator has a command's input come from a file or stream, or sends its output to a file or stream.
Read from stdin until ^EOF$
and overwrite hello.sh
with the lines
between "EOF" (which are called a "here document"):
cat > /tmp/hello.sh << EOF
#!/usr/bin/env bash
# read stdin and print it to stdout
stdin=$(cat)
if [[ "$stdin" == "" ]]; then
echo "stdin is empty"
else
echo "stdin: $stdin"
fi
EOF
Variables will be expanded if the first "EOF" is not quoted.
Run the hello.sh
Bash script with various stdin, stdout, and
stderr redirections:
# pass input.in as input to the script
bash hello.sh < "input.in"
# redirect output from the script to output.out
bash hello.sh > "/tmp/output.out"
# redirect error output to error.err
bash hello.sh 2> "/tmp/error.err"
stdin: I appreciate your input.
stdin is empty
Redirect both output and errors to output-and-error.log
&1
means file descriptor 1 (stdout), so 2>&1
redirects stderr (2) to the current
destination of stdout (1), which has been redirected to output-and-error.log
:
bash hello.sh > "/tmp/output-and-error.log" 2>&1
cat /tmp/output-and-error.log
stdin is empty
Redirect all output and errors to the black hole, /dev/null
, i.e., no output:
bash hello.sh > /dev/null 2>&1
>
will overwrite the file if it exists. If you want to append instead, use >>
:
bash hello.sh >> "/tmp/output.out" 2>> "/tmp/error.err"
bash hello.sh >> "/tmp/output.out" 2>> "/tmp/error.err"
echo "output.out:"
cat /tmp/output.out
echo "error.err:"
cat /tmp/error.err
output.out:
stdin is empty
stdin is empty
error.err:
Overwrite output.out
, append to error.err
, and count lines:
help for > /tmp/output.out 2>> /tmp/error.err
wc -l /tmp/output.out /tmp/error.err
10 /tmp/output.out
0 /tmp/error.err
10 total
Run a command and print its file descriptor (e.g. /dev/fd/123
)
see: man fd
echo <(echo "#helloworld")
/dev/fd/63
Overwrite output.out
with #helloworld
:
cat > /tmp/output.out <(echo "#helloworld")
echo "#helloworld" > /tmp/output.out
echo "#helloworld" | cat > /tmp/output.out
echo "#helloworld" | tee /tmp/output.out >/dev/null
cat /tmp/output.out
#helloworld
Cleanup temporary files verbosely (add -i
for interactive).
WARNING: rm
commands cannot be undone:
touch /tmp/one.txt /tmp/two.txt
mkdir /tmp/subdir
touch /tmp/subdir/three.txt
rm -v /tmp/one.txt /tmp/two.txt
rm -r /tmp/subdir/ # recursively delete
removed '/tmp/one.txt'
removed '/tmp/two.txt'
You can install the trash-cli
Python package to have trash
which puts files in the system trash and doesn't delete them directly.
Commands can be substituted within other commands using $( )
.
The following command displays the number of files and directories in the
current directory:
echo "There are $(ls | wc -l) items here."
There are 1 items here.
The same can be done using backticks ``but they can't be nested -
the preferred way is to use$( )
.
echo "There are `ls | wc -l` items here."
There are 1 items here.
Running processes
A single ampersand &
after a command runs it in the background. A background command's
output is printed to the terminal, but it cannot read from the input.
sleep 30 &
# List background jobs
jobs # => [1]+ Running sleep 30 &
# Bring the background job to the foreground
fg
# Ctrl-C to kill the process, or Ctrl-Z to pause it
# Resume a background process after it has been paused with Ctrl-Z
bg
# Kill job number 2
kill %2
# %1, %2, etc. can be used for fg and bg as well
Redefine command ping
as alias to send only 5 packets:
alias ping='ping -c 5'
Escape the alias and use command with this name instead:
\ping -c 1 127.0.0.1
PING 127.0.0.1 (127.0.0.1): 56 data bytes
64 bytes from 127.0.0.1: seq=0 ttl=42 time=0.114 ms
--- 127.0.0.1 ping statistics ---
1 packets transmitted, 1 packets received, 0% packet loss
round-trip min/avg/max = 0.114/0.114/0.114 ms
Print all aliases:
alias -p
alias ping='ping -c 5'
Useful commands
There are a lot of useful commands you should learn.
Prints last 5 lines of data.txt
:
tail -n 5 data.txt
three
four,five
foo
foo bar
foo baz bar
Prints first 5 lines of data.txt
:
head -n 5 data.txt
one
two
two
three
three
Print data.txt
's lines in sorted order:
sort data.txt
foo
foo bar
foo baz bar
four,five
one
three
three
three
two
two
Report or omit repeated lines, with -d
it reports them:
uniq -d data.txt
two
three
Prints only the first column before the ,
character:
cut -d ',' -f 1 data.txt | grep four
four
Replaces every occurrence of three
with ten
in data.txt
(regex compatible):
cp data.txt /tmp
sed -i 's/three/ten/g' /tmp/data.txt
grep "three" /tmp/data.txt | wc -l
grep "ten" /tmp/data.txt | wc -l
0
3
Be aware that this -i
flag means that data.txt
will be changed.
-i
or --in-place
erase the input file (use --in-place=.backup
to keep a back-up).
Print to stdout all lines of data.txt
which match some regex.
The example prints lines which begin with foo
and end in bar
:
grep "^foo.*bar$" data.txt
foo bar
foo baz bar
Pass the option -c
to instead print the number of lines matching the regex:
grep -c "^foo.*bar$" data.txt
2
Other useful options are:
# recursively `grep`
grep -r "^foo.*bar$" somedir/
# give line numbers
grep -n "^foo.*bar$" data.txt
# recursively `grep`, but ignore binary files
grep -rI "^foo.*bar$" somedir/
Perform the same initial search, but filter out the lines containing "baz"
grep "^foo.*bar$" data.txt | grep -v "baz"
foo bar
If you literally want to search for the string,
and not the regex, use fgrep
(or grep -F
):
fgrep "baz" data.txt
foo baz bar
The trap
command allows you to execute a command whenever your script
receives a signal. Here, trap
will execute rm
if it receives any of the
three listed signals.
trap "rm $TEMP_FILE; exit" SIGHUP SIGINT SIGTERM
sudo
is used to perform commands as the superuser.
Usually it will ask interactively the password of superuser:
NAME1=$(whoami)
NAME2=$(sudo whoami)
echo "Was $NAME1, then became more powerful $NAME2"
Read Bash shell built-ins documentation with the bash help
built-in:
help
help <command>
Help for the return
command:
help return
return: return [n]
Return from a shell function.
Causes a function or sourced script to exit with the return value
specified by N. If N is omitted, the return status is that of the
last command executed within the function or script.
Exit Status:
Returns N, or failure if the shell is not executing a function or script.
Read Bash manpage documentation with man
:
apropos bash
man 1 bash
man bash
Read info documentation with info
(?
for help):
apropos info | grep '^info.*('
man info
info info
info 5 info
Read bash info documentation:
info bash
info bash 'Bash Features'
info bash 6
info --apropos bash
Max Yankov + 16 others · original · CC-BY-SA-4.0 · 2024-02-12
Max Yankov, Darren Lin, Alexandre Medeiros, Denis Arh, akirahirose, Anton Strömkvist, Rahil Momin, Gregrory Kielian, Etan Reisner, Jonathan Wang, Leo Rudberg, Betsy Lorton, John Detter, Harry Mumford-Turner, Martin Nicholson, Mark Grimwood, Emily Grace Seville