Introduction
The shell
is the command interpreter: executes the given commands one by one.
loop forever
<accept command from console>
<execute command>
end loop;
shell
is a command processor that accept commands given from the terminal (or from a command file) and execute them until the file’s end.
As a convention, inside all the shell snippets written on those blog posts, $
symbol (dollar sign) and a space will precede a command you could launch on your Terminal.
You must not write the $
and the space on your Terminal window, but what’s after it.
I’ll also write the output in the lines right below that command.
Here’s an example:
COMMAND
# OUTPUT OF THE COMMAND
# my own comments about what's happening
Do not confuse COMMAND
with $COMMAND
, which is a variable named COMMAND
.
^KEY
means the key combination Ctrl
+ KEY
. So ^C
means Ctrl
+ C
, ^X
means Ctrl
+ X
and so on.
Here are some useful key combinations:
^C
stops the executing of a command in the Terminal^L
clears the screen and place the cursor back on top
Moreover, I really suggest you to read UNIX Introduction article before moving on. Anyway, let’s make a very brief recap about wild cards and parsing.
Special characters
Wild cards are also named metacharacters or special characters. They allow the shell to execute pattern matching between a string and file names in the present working directory.
The symbol *
abbreviates any string of zero or more characters in a file name:
[ccc...]
stands for any character in a file name, between the elements the square brackets.
[c-c...]
stands for any character in a file name, in all the elements between the extremes of the set(s) in the square brackets.
The hash symbol #
comments until the end of the line (as you can see in almost all the examples).
The backslash (\
symbol) is the escape metacharacter: it tells bash not to interpret the subsequent character as a special character.
Please Note:
- Current directory is identified by the single dot (
.
symbol). - Parent directory of a given directory is identified by a couple of dots (
..
).
Parsing
Before executing, the command line is parsed to look after special characters. The very first parsed metacharacters are redirection and piping. In the subsequent scanning, if the shell find any other special character, result in one of the following substitutions:
- command: commands place among
“
(backquotes) are executed and the result is produced, - variable & parameters: variables’ names (
$VAR_NAME
) are substituted with their actual values, - file names:
*
,?
,[]
metacharacters are substituted into the file names using pattern matching
Moreover, the use of the quotes (single and double) allow and disallow certain types of substitutions:
- Single quotes disallow all the substitution types
- Double quotes only disallow file names substitutions
Both single and double quotes disallow the shell to interpreter redirection and piping metacharacters.
In addition, eval
command executes arguments as a shell command. It allows an additional phases of substitution.
Program the shell
The command processor can elaborate commands taking them from a file which is usually called command file.
A commands file can contain:
- passing of parameters
- variables
- control flow statements
Note that what instructions are available depends on what shell is being used.
Please Note: shell, meant as a programming language, is interpreted (not compiled).
Shell types
In modern Linux (and Unix) systems, there are many types of shells:
- sh: Bourne Shell
- bash: Bourne Again Shell, advanced sh version
- zsh: Z Shell, very advanced sh version
- ksh: Korn Shell
- csh: C Shell, syntax similar to C language
- tcsh: Turbo C Shell, advanced csh version
- rush: Ruby Shell, Ruby-based shell
- hotwire
Hashbang
Based on the type of shell is being used, the command file, that below will simply be called script, must have a different hashbang (or shebang).
The hashbang is the first line of the script, it tells the shell to run the given script with the specified command.
It’s not mandatory for the hashbang line to be an executable of a shell.
It can be anything, even the cat
command: #!/bin/cat
.
Note that it must start with #!
(while ordinary comments only starts with #
).
Here are some hashbang for the shells above:
#!/bin/sh
#!/bin/bash
#!/bin/zsh
# ...
Passing parameters
Arguments are positional variables in the invocation line.
$0
represents the name of the command file with is executing.
$1
represents the 1st argument, $2
represents the 2nd argument and so on.
echo $0
# /bin/bash
It’s possible to slide all arguments from right to left (shifting).
$0
will never be lost.
Important variables
$*
stores the set of positional variables which correspond to the command arguments ($1
, $2
, …).
$#
stores the number of passed arguments (excluding $0
).
$?
stores the integer value returned by the last executed command.
If $?
is equal to 0
, the command has been executed correctly. If $?
is major than 0
(holds an integer value), an error occurred during command execution.
$$
stores the PID (Process Identifier) of the process which is executing.
Other important variables are: $PATH
, $HOME
, $UID
and $IFS
.
$IFS
contains the list of characters used as input separators.
The default value is space, tab and new line (\n
).
You can modify $IFS
when you need to process an input made up by data where spaces and tabs have a very different meaning.
Command Execution
To run your shell script, need to make it executable:
chmod +x my-command
# give execute permissions to all
# .sh files in the current directory
chmod +x *.sh
The shell looks for commands to execute only inside the directories specified in PATH
environment variable.
Paths are separated by colon sign:
echo $PATH
/home/pit/.local/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games:/usr/local/games:/snap/bin
# display them in a more readable way
echo $PATH | tr ':' '\n'
# /home/pit/.local/bin
# /usr/local/sbin
# /usr/local/bin
# /usr/sbin
# /usr/bin
# /sbin
# /bin
# /usr/games
# /usr/local/games
# /snap/bin
To execute a command that is not in a directory listed by PATH
variable, you need to specify its absolute path.
If you want to execute a command placed in the current working directory:
./my-command
You can also invoke a shell to execute the command, it will bypass the hashbang:
# SHELL my-command
sh my-command
bash my-command
zsh my-command
In this way, you don’t need to use the prefix ./
since the shell does not look for commands in the paths of PATH
variable, but simply runs my-command
(whose name is passed as command line argument).
Given this short script, named my-script.sh
:
#!/bin/cat
pwd
echo $0
echo $HOME
If you run it, it will execute cat
command over the script:
chmod +x my-script.sh
./my-script.sh
#!/bin/cat
pwd
echo $0
echo $HOME
But if you invoke sh
shell to execute the command, it will bypass the cat
hashbang:
sh ./my-script.sh
# /home/pit/Documents
./my-script.sh
# /home/pit
Input & Output
To get the input from the keyboard you can use the read
command.
read
reads a single input line and assign its value to the variables passed as parameters. It consider as input separators those defined in $IFS
.
read hello world
# Hello, World!
# write the above line on the Terminal
echo $hello
# Hello,
echo $world
# World!
echo $hello $world
# Hello, World!
To avoid errors while parsing the input, it’s better to launch a read
for each variable you need to create.
echo
command, as you saw many times, outputs a line of text on the standard output.
You can also redirect the output to a file using >
sign.
Data types
Variables
Variables are all strings. You must avoid spaces between =
sign during assignment, otherwise the shell won’t understand what you’re doing.
hello="Hello, World!"
echo $hello
# Hello, World!
Arrays
Bourne Shell (sh) does not support arrays. You can simulate them in this way:
n=1
eval arr[$n]=a
eval echo \${arr[$n]}
a # output
n=2
eval arr[$n]=b
eval echo \${arr[$n]}
b # output
n=3
eval arr[$n]=c
eval echo \${arr[$n]}
c # output
n='*'
eval echo \${arr[$n]}
a b c # output
Note that many other types of shell does support arrays.
Control Structures
There are a couple of commands that are mandatory to understand and know how to use.
expr command evaluates arithmetical expressions taking string arguments.
test command is used in almost all control structures.
if
if <commands-list>
then
<comands>
[else <comands>]
fi
Note that keywords (do
, then
, fi
, …) must be placed after ;
separator or in the line below.
if
checks the output value of the last command from <commands-list>
.
Here’s a simple example:
#!/bin/sh
x="a"
y="a"
# both if statements are equivalent
# if [ $x = $y ]
if test "$x" = "$y"
then echo "equal"
else echo "NON equal"
fi
The output will always be equal
unless you change the values of x
and y
variables.
case
case <var> in
<pattern-1>) <comands> ;;
<pattern-i> | <pattern-j> | <pattern-k>) <comands> ;;
<pattern-n>) <comands> ;;
esac
case-test.sh
contents is:
#!/bin/sh
case $1 in
"NULL" ) echo "The input is NULL" ;;
S* | s* | Y* | y* ) echo "Approved" ;;
*) echo "Default branch: none of the above"
esac
The 1st case is given by:
./case-test.sh NULL
The input is NULL
The 2nd case:
./case-test.sh Yes
./case-test.sh yellow
./case-test.sh Sunglesses
./case-test.sh silent
Approved # all the above lines gets this output
The default branch is:
./case-test.sh car
./case-test.sh Butter
./case-test.sh Null # shell is case sensitive
Default branch: none of the above
# all the above lines gets this output
check if a parameter is a number
The script check-int.sh
accepts one argument and ignore all the others.
#!/bin/sh
# save 1st argument into $num
num="$1"
# check if $num is (or not) a numeric value
case $num in
*[!0-9]*) echo "$num is not a number"; exit 1 ;;
*) echo "$num is a number";;
esac
The output is:
./_check-int.sh abc
# abc is not a number
./_check-int.sh 123
# 123 is a number
./_check-int.sh A123
# A123 is not a number
./_check-int.sh 456Z
# 456Z is not a number
for
for <var> [in <list of strings>]
do
<comands>
done
The sketch below prints all the elements in the current directory:
#!/bin/bash
for i in *
do
echo "print: "$i
done
cmd-args.sh
script loops over the command arguments and prints them
echo "positional variables are:"
for i in $*
do
echo $i
done
Let’s test it out:
./cmd-args.sh /dev/ hello good
positional variables are:
/dev/
hello
good
while
while <commands-list>
do
<comands>
done
while-test.sh
script prints on the standard output numbers from 0
to $1
:
#!/bin/bash
a=0
while test $a -le $1
do
echo $a
a=`expr $a + 1` # increment
done
The output is:
./while-test.sh 3
0
1
2
3
# if $1 is not an integer number, throws an error
./while-test.sh hello
./while-test.sh: line 4: test: hello: integer expression expected
until
until <commands-list>
do
<comands>
done
It’s a while
loop with the inverse of the entry condition.
while-test.sh
script prints on the standard output numbers from $1
to 0
:
#!/bin/bash
a=$1
until test $a -le 0
do
echo $a
a=`expr $a - 1` # decrement
done
The output is:
./until-test.sh 3
3
2
1
# if $1 is not an integer number
# enter an infinite loop throwing errors
./until-test.sh hello
expr: syntax error: unexpected argument ‘1’
./until-tests.sh: line 4: test: -le: unary operator expected
# you need to manually stop the script
^C
select
select <variable> in <list-of-variables>
do
case <variable> in
<pattern-i>) <comands> ;;
<pattern-j>) <comands> ;;
<pattern-k>) <comands> ;;
esac
done
Override the content of PS3
variable to change the message displayed to the user before the choice.
#!/bin/bash
PS3="Choice: "
echo "Where do you live? "
select answer in Rome Paris Copenaghen none
do
case $answer in
Rome) echo "Rome is the capital of Italy." ;;
Paris) echo "Look the Eiffel Tower." ;;
Copenaghen) echo "The most populous city of Denmark." ;;
none) break ;;
*) echo "You need to enter an item from the list!" ;;
esac
done
The output is:
./select-test.sh
Where do you live?
1) Rome
2) Paris
3) Copenaghen
4) none
Choice: 1 # user input: 1
Rome is the capital of Italy.
Choice: 3
The most populous city of Denmark.
Choice: 2 # user input: 2
Look the Eiffel Tower.
Choice: 78 # user input: 78
You need to enter an item from the list!
Choice: 4 # user input: 4, which is the exit case
Quotes
References: