By Sheldon L | Published at 2019-11-19 | Updated at 2019-11-19 |
Linux provides a wide choice of shells; exactly what is available on the system is listed in /etc/shells
.
Examples of Shebang:
#!/bin/csh
#!/bin/ksh
#!/bin/zsh
#!/usr/bin/python
HelloWorld.sh
cat << EOF > hello.sh
> #!/bin/bash
> echo "Hello, world"
> EOF
bash hello.sh
# OR
chmod +x HelloWorld.sh
./HelloWorld.sh
VARIABLE_NAME="Value"
$VARIABLE_NAME
or ${VARIABLE_NAME}
if need to immediately precede or follow the variable with additional data.
#!/bin/bash
MY_SHELL = "bash"
echo "I like the $MY_SHELL shell."
echo "I am ${MY_SHELL}ing on my keyboard."
$()
, which allows command nesting``
, which is deprecated in new scripts and commands#!/bin/bash
SERVER_NAME=$(hostname)
echo "Running this script on ${SERVER_NAME}."
ls /lib/modules/`uname -r`
Shell scripts execute sequences of commands and other types of statements. These commands can be:
Shell scripts or scripts from other interpreted languages, such as perl and Python.
Compiled applications are binary executable files, generally residing on the filesystem in well-known directories such as /usr/bin
. Shell scripts always have access to applications such as rm
, ls
, df
, vi
, and gzip
, which are programs compiled from lower level programming languages such as C.
bash
has many built-in commands, which can only be used to display the output within a terminal shell or shell script. Sometimes, these commands have the same name as executable programs on the system, such as echo which can lead to subtle problems. bash
built-in commands include cd
, pwd
, echo
, read
, logout
, printf
, let
, and ulimit
. Thus, slightly different behavior can be expected from the built-in version of a command such as echo as compared to /bin/echo. A complete list of bash built-in commands can be found in the bash man page, or by simply typing help
in terminal, as we review on the next page.
Parameter | Meaning |
$0 |
Script name |
$1 , $2 , $3 , etc. |
First, second, third parameter, etc. |
$* |
All parameters |
$# |
Number of arguments |
$@ |
from $1 to the last one |
archive_user.sh
#!/bin/bash
echo "Executing script: $0"
echo "Archiving user: $1"
# Lock the account
passwd -l $1
# Create an archive of the home directory
tar cf /archives/${1}.tart.gz/home/${1}
./archive_user.sh user_name
#!/bin/bash
echo "Exacuting script: $0"
for USER in $@
do
echo "Executing script: $0"
echo "Archiving user: $USER"
# Lock the account
passwd -l $USER
# Create an archive of the home directory
tar cf /archives/${USER}.tart.gz /home/${USER}
done
./archive_user.sh user_name_1 user_name_2 user_name_n
read -p "PROMPT" VARIABLE_NAME
#!/bin/bash
read -p "Enter a user name:" USER
echo "Archiving user: $USER"
passwd -l $USER
tar cf /achives/${USER}.tar.gz /home/${USER}
$?
”$?
contains the return code of the previously executed commandHOST = "google.com"
ping -c 1 $HOST
RETURN_CODE = $?
if ["$RETURN_CODE" -ne "0"]
then
echo "$HOST unreachable"
fi
exit
Commandexit $NUMBER
where the NUMBER
is from 0
to 255
HOST="google.com"
ping -c 1 $HOST
if [ "$?" -ne "0" ]
then
echo "HOST unreachable."
exit 1
fi
#!/bin/bash
function fuction_name(){
commands...
}
# OR
fuction_name(){
commands...
}
# call function
function_name
Arithmetic expressions can be evaluated in the following three ways (spaces are important!):
expr
, a standard but somewhat deprecated program:expr 8 /* 8
echo $(expr 8 /* 8) # can not tell *, should use /*
$((...))
, the built-in shell format. In modern shell scripts, expr
is better replaced with var=$((...))
:echo $((x+1))
let
:let x=( 1 + 2 ); echo $x
${#string} # the length of string in the variable myLen1.
${string:0:n} # slicing
${string#*.} # extract all characters in a string after a dot (.)
NAME=Eddie.Haskel
first=${NAME:0:5} ; echo "first name = $first"
last=${NAME#*.} ; echo "last name = $last"
if
statementif
statement to test for file attributes, such as:
[[ CONDITION (is True) ]]
# Files and directorys
[[ -e file ]] # True if exists
[[ -d file ]] # True if is a directory
[[ -f file ]] # True if exists and is a regular file (i.e. not a symbolic link, device node, directory, etc.)
[[ -s file ]] # True if exists and s of non-zero size.
[[ -r file ]] # True if is readable
[[ -w file ]] # True if is writable
[[ -x file ]] # True if is executable
[[ -g file ]] # True if the file has sgid set.
[[ -u file ]] # True if the file has suid set.
# Strings Testing
[ -z string ] # True if is empty
[ -n string ] # True if is not empty4
# note if $str1 is empty, the test [ -z $str1 ] would fail
# but [[ -z $str1 ]] succeeds
[[ string1 == string2 ]]
[[ string1 != string2 ]]
[[ string1 > string2 ]] # the sorting order of string1 and string2
# Numerical Tests
[[ arg1 -eq arg2 ]] # =
[[ arg1 -ne arg2 ]] # !=
[[ arg1 -lt arg2 ]] # <
[[ arg1 -le arg2 ]] # <=
[[ arg1 -gt arg2 ]] # >
[[ arg1 -ge arg2 ]] # >=
if
statement:if [ CONDITION ]
then
commands...
elif [ CONDITION ]
then
commands...
else
commands...
fi
In modern scripts, you may see doubled brackets as in [[ -f /etc/passwd ]]
. This is not an error. It is never wrong to do so and it avoids some subtle problems, such as referring to an empty environment variable without surrounding it in double quotes.
[[ ]]
works even without the quotes[[ -z $str ]]
even succeed if the string is zero length! Should use [ -z $str ]
#!/bin/bash
MY_SHELL="bash"
if [ "$MY_SHELL"="bash" ] # it's a best practice to enclose variables in quotes
# to prevent some unexpected side effects
# when performing conditional tests
then
echo "You seem to like the bash shell."
elif [ "$MY_SHELL"="csh" ]
then
echo "You seem to like the csh shell."
else
echo "You don't seem to like the bash or csh shells."
fi
&&
and ||
)command_1 && command_2
:
if ["$?" -eq "0"]
then do command_2#!/bin/bash
HOST = "google.com"
ping -c 1 $HOST && echo "$HOST reachable"
command_1 || command_2
:
if ["$?" -ne "0"]
then do command_2#!/bin/bash
HOST = "google.com"
ping -c 1 $HOST || echo "$HOST unreachable"
;
”, the semicolon doesn’t perform exit status checking, nomatter if the previous command faild or successed, the command following a semicolon will always get executed.make ; make install ; make clean
if [[ CONDITION1 ]] ; then do_commands
elif [[ CONDITION2 ]] ; then do_commands
else
do_commands
fi
make && make install && make clean
[[ CONDITION1 ]] && [[ CONDITION2 ]] && do_commands
If the first command fails, the second one will never be executed. A final refinement is to use the | (or) operator, as in: |
cat file1 || cat file2 || cat file3
case
StatementBelow are some of the advantages of using the case statement:
if-then-else-fi
code blocks.case expression in
pattern1) execute commands;;
pattern2) execute commands;;
pattern3) execute commands;;
pattern4) execute commands;;
* ) execute some default commands or nothing ;;
esac
#!/bin/bash
echo "Do you want to destroy your entire file system?"
read response
case "$response" in
"yes" | "y" | "Yes" | "Y" ) echo "I hope you know what you are doing!"
echo "I am supposed to type: rm -rf /"
echo "But I refuse to let you commit suicide.";;
"no" | "n" | "No" | "N" ) echo "You are smart! Aborting...";;
* ) echo "You have to say yes or no.";;
esac
exit 0
for
statementsfor VARIABLE in ITEM_1 ITEM_2 ITEM_n
do
commands...
done
#!/bin/bash
COLORS="red green blue"
for COLOR in $COLORS
do
echo "Color: $COLOR"
done
#!/bin/bash
PICTURES=$(ls *jpg)
DATE=$(date +%F)
for PICTURE in $PICTURES
do
echo "Renaming '${PICTURE}' to '${DATE}-${PICTURE}'"
mv ${PICTURE} ${DATE}-${PICTURE}
done
while
statementswhile [ condition is true ]
do
commands
done
#!/bin/bash
n=$1
[ "$n" == "" ] && echo "Please give a number and try again" && exit 1
factorial=1 ; j=1
while [ $j -le $n ]
do
factorial=$(( $factorial * $j ))
j=$(( $j + 1 ))
done
echo "The factorial of $n, $n! = $factorial"
until
statementsuntil [ condition is false ]
do
commands
done
#!/bin/bash
n=$1
[ "$n" == "" ] && echo "Pleas give a number and try again" && exit 1
factorial=1 ; j=1
until [ $j -gt $n ]
do
factorial=$(( $factorial * $j ))
j=$(( $j + 1 ))
done
echo "The factorial of $n, $n! = $factorial"
bash -x ./script_file
Or bracketing parts of the script with set -x
and set +x
. It can debug only selected parts.
bash [script].sh 2> error.log # because the File Descriptor of `stderr` is 2, see "file stream"
# default redirect File Descriptor is 1, so `>` will redirect to `stdout`, whose descriptor is 1.
$ at now + 2 days
at> cat file1.txt
at> <EOT> # press Ctrl+D here
# CROND # driven by /etc/crontab (cron table), run every day/week/month...
crontab -e # 0 10 * * * /tmp/myjob.sh
# OR
touch mycrontab
echo `0 10 * * * /tmp/myjob.sh` > mycrontab
crontab mycrontab
crontab -l # list
crontab -r # remove
touch /tmp/myjob.sh
echo '#!/bin/bash' > /tmp/myjob.sh
echo 'echo Hello I am running $0 at $(date)' >> /tmp/myjob.sh
chmod +x /tmp/myjob.sh
sleep [number]<s|m|h|d> # suspends execution for at least the specified period of time
mktemp
utility.# file
TEMP=$(mktemp /tmp/tempfile.XXXXXXXX) # XXXXXXXX will be replaced by mktemp
# dir
TEMPDIR=$(mktemp -d /tmp/tempdir.XXXXXXXX)
# For example, if someone were to create a symbolic link from a known temporary file
# used by root to the `/etc/passwd` file, like this:
ln -s /etc/passwd /tmp/tempfile
# There could be a big problem if a script run by root has a line in like this:
echo $VAR > /tmp/tempfile
# The password file will be overwritten by the temporary file contents.
# To prevent such a situation, make sure you randomize your temporary file names
# by replacing the above line with the following lines:
TEMP=$(mktemp /tmp/tempfile.XXXXXXXX)
echo $VAR > $TEMP
Certain commands (like find) will produce voluminous amounts of output, which can overwhelm the console. To avoid this, we can redirect the large output to a special file (a device node) called /dev/null
. This pseudofile is also called the “bit bucket” or “black hole”.
All data written to it is discarded and write operations never return a failure condition. Using the proper redirection operators, it can make the output disappear from commands that would normally generate output to stdout
and/or stderr
:
ls -lR /tmp > /dev/null # only stdout will be dumped into /dev/null.
ls -lR /tmp >& /dev/null # both stdout and stderr will be dumped into /dev/null.
It is often useful to generate random numbers and other random data when performing tasks such as:
$RANDOM
environment variable, which is derived from the Linux kernel’s built-in random number generator:
Some servers have hardware random number generators that take as input different types of noise signals, such as thermal noise and photoelectric effect. A transducer converts this noise into an electric signal, which is again converted into a digital number by an A-D converter. This number is considered random. However, most common computers do not contain such specialized hardware and, instead, rely on events created during booting to create the raw data needed.
Regardless of which of these two sources is used, the system maintains a so-called entropy pool of these digital numbers/random bits. Random numbers are created from this entropy pool.
The Linux kernel offers the /dev/random
and /dev/urandom
device nodes, which draw on the entropy pool to provide random numbers which are drawn from the estimated number of bits of noise in the entropy pool.
/dev/random
is used where very high quality randomness is required, such as one-time pad or key generation, but it is relatively slow to provide values. /dev/urandom
is faster and suitable (good enough) for most cryptographic purposes.
Furthermore, when the entropy pool is empty, /dev/random
is blocked and does not generate any number until additional environmental noise (network traffic, mouse movement, etc.) is gathered, whereas /dev/urandom
reuses the internal pool to produce more pseudo-random bits.