Cheatsheet on bash scripting

Home

1 Bash shell basics for file zintscript.sh

1.1 #!/bin/bash

This is always the first line of the script

1.2 Execute it using bash:

bash zintscript.sh

1.3 Execute it independently using chmod

Make the script executable with

chmod 740 zintscript.sh
./zintscript.sh

1.4 One command per line, flush left

touch mynewfile.org
chmod 740 mynewfile.org

1.5 Splitting one long command over multiple lines

You can escape the newline character, with a \ which will let you continue a long command on the next line.

awk '/^\#\+DATE/{print; \
!/^\#\+DATE/{print} \
END {print "this is the end"}' junk.org

1.6 Variables $x

# Create a variable:
mynewvar='gold'

# Read a variable
if $mynewvar = 'gold' :    # notice the colon :

NETIP=172.17.17.

LOCALIP="$(command)"
LOCALIP="$(IP ADDR | egrep '\''([0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3})'\'')"

1.7 Looping with "for"

This loop converts some markdown (*.md) files to org-mode files using pandoc. I have not actually tried pandoc, but it looks promising.

for f in 'ls *.md'; do
    pandoc -f markdown -t org -o ${f}.org ${f};
done

1.8 Giving bash scripts options

The standard format is command --options -a arguments from the command line.

From opensource.com article on this:

The strategy for parsing options in Bash is to cycle through all arguments passed to your shell script, determine whether they are an option or not, and then shift to the next argument. Repeat this process until no options remain.

#!/bin/bash
while [ True ]; do
 if [ "$1" = "--euler" -o "$1" = "-e" ]; then
     EULER=2718281828459
     shift 1
 else
     break
 fi
 done

 echo $EULER

In this code, I create a while loop which serves as an infinite loop until there are no further arguments to process. An if statement attempts to match whatever argument is found in the first position ($1) to either –euler or -e. The Euler number here is just used as an example.

The shift keyword causes all arguments to shift by 1, such that an argument in position 2 ($2) is moved into position 1 ($1). The else statement is triggered when there are no further arguments to process, which breaks the while loop.

At the end of the script, the value of $EULER is printed to the terminal.

1.9 Giving bash scripts more options

From the above section, we would only catch the single –alpha or -a option.

To get the rest, we can store the remaining options in an array.

#!/bin/bash
while [ True ]; do
 if [ "$1" = "--euler" -o "$1" = "-e" ]; then
     EULER=2718281828459
     shift 1
 else
     break
 fi
 done

 echo $EULER

 ARG=( "${@}" )
 for i in ${ARG[@]}; do
     echo $i
 done

Test

1.10 From github:

#!/bin/bash

# viewing environment variables
echo "The value of the home variable is: "
echo $HOME

# issue a command
echo "The output of the pwd command is: "
pwd

# grab output and make it readable using $(command)
echo "The value of the pwd command is $(pwd)"

# assign command output to a variable ${variable}
output=$(pwd)
echo "The value of the output variable is ${output}"
# ${} reminds you that inside the curlies is a variable

# view data on the command line  $@
echo "I saw $@ on the command line"



# read data from the user
echo "Enter a value: "

read userInput
echo "You just entered $userInput"

# concatenate userinput with command output
echo "Enter a file extension: "
read ext
touch "yourfile.${ext}"

# check to see if a file exists
if [ -d /etc/sysconfig ]; then
        echo "That file is there and a directory"
else
        echo "Not there or not a directory"
fi

for i in {10..30} ; do echo $i ; done | xargs  -I_ touch deletme-file-_.txt

for f in ./deleteme*.*; do
       cat add-a-line >> $f;
done

1.11 Bash shell expansion {..}

Related to the the above example, is the bash shell expansion of ranges of characters using the {..} syntax.

touch files-{A..G}.data

will result in these files

files-A
files-B
files-C
files-D
files-E
files-F
files-G

2 Bash script built-in and environement variables

To read more about these built-in variables, see man bash as always.

2.1 $1

$1 is the first argument included when running this script. Likewise $2 and $3 etc are the second and third arguments.

Which shows you that $* expands ALL space delimited arguments as ONE argument while $@ expands ALL space delimited arguments each as their own SEPARATE arguments.

2.2 $@ (all args)

bash will expand $@ to all the arguments attached to the calling shell command, with each space delimited arguement as its own SEPARATE argument. For example:

for FILE in "$@";
   do
      mv "$FILE" ~/Trash/;
   done

IF you run this with an argument *.org then each file ending in .org will be passed to the for loop ONE AT A TIME

Really the same as expanding to $1 $2 $3 ... $n

2.3 $* is very similar to $@ (all args as ONE string)

But they are not interchangeable, because $* takes ALL the arguments as one long string arguement.

2.4 bash script to display $@ and $*

This website, the geek stuff, had this sample script:

#!/bin/bash
# show me how bash special args expand out.  Script is called expan.sh
 export IFS='-'

 cnt=1

 # Printing the data available in $*
 echo "Values of \"\$*\":"
 for arg in "$*"
 do
   echo "Arg #$cnt= $arg"
   let "cnt+=1"
 done

 cnt=1

 # Printing the data available in $@
 echo "Values of \"\$@\":"
 for arg in "$@"
 do
   echo "Arg #$cnt= $arg"
   let "cnt+=1"
 done

When executed with you get

$ ./expan.sh "This is" 2 3
 Values of "$*":
 Arg #1= This is-2-3
 Values of "$@":
 Arg #1= This is
 Arg #2= 2
 Arg #3= 3

Which shows you that $* expands ALL space delimited arguments as ONE argument while $@ expands ALL space delimited arguments each as their own SEPARATE arguments.

2.5 $# (count)

Returns a count of how many positional arguments exist. If you run a script like this: myscript.sh 55 two three then $# will return the value 3 because there were three positional arguments to the bash script.

2.6 $$ and $! (process id)

Whenever bash runs a script, it assigns a process id, which can be recalled with $$ At the same time, $! will return the process id of the last background process that was executed.

2.7 $? (exit status)

Returns the exit status of the most recently executed script, or command.

2.8 $- (options)

Returns the options set using the built-in set command.

2.9 $_ (last arg)

Returns the last argument to the previous command. At the shell startup, it gives the absolute filename of the shell script being executed.

2.10 Environment variables

You can check what variables you have with the command printenv Or you can expand that and check ALL system environment variables with the command set

Then use the in scripts for instance:


3 Wrapper script for running pyenv python in crontab

Let's say you want to run covid.py every morning, but it needs python 3.7.5 which is set up in your pyenv setting.

In your crontab run 59 7 * * * /Users/zintis/wrapper.sh

And wrapper.sh looks like:

#!/bin/bash -l   // this picks up your ~/.bash_profile environment variables
cd /Users/zintis/bin/python/bin  // needed only if this dir is not in your python path
source /Users/zintis/.virtualenv/dslab/bin/activate  // activate your pyenv
python /Users/home/covid.py    // actually run your script.

3.1 alternatively,

which python > Users/zintis.pyenv/shims/python then but that as the shebang in your covid program. i.e. #!/Users/zintis/.pyenv/shims/python

3.2 alternatively, set env right in crontab

SHELL=/usr/local/bin
PATH=/usr/local/sbin:/usr/local/bin/ <continues your PATH>
30 12,15,18 * * 1,2,3,4,5 root export VAR1=value1 && export VAR2=value2 && export VAR3=value3 && export VAR4=value4 /usr/bin/python3 /path_to_script/Kwanty_bez_eng.py

4 Standard File Descriptors

There are two standard output file descriptors

  • 1 is stdout
  • 2 is stderr

By default both are the terminal where the command was entered.

When running a script from a cron job, i.e NOT from the standard terminal you can redirect &1 and &2 to some other location, typically a file.

  • batchjob.py > ~/batchoutput 2>&1

This means the standard output, stdout, is the file ~/batchjoboutput and to also redirect stderr to the same place. i.e. redirect 2 to what &1 is.

4.1 Common errors

2>1 it would simply rediredt standard errors to a file called '1'

job &2>&1 it would run "job" in the background, & There does not have to be a space between the first & and the 2, so that simply takes "2" which is stderr and redirects it to, in this case &1, which is stdout.

5 Logical Operators, &&, ||

  • x && y ==> =y only if x true i.e x had to complete successfully before you do y
  • x || y ==> y only if x false i.e only do y if x failed

These can often let you avoid using cubmersome if statements in your bash scripts.

Below are details for each type. Remember that $? is a shell variable that holds the error condition from the last executed command. 0 if it worked, 1 if it failed.

5.1 and operator && only execute the 2nd part if the first PASSED

ansible@c8host ~[1174] $
true && echo "ok it worked"
ok it worked

ansible@c8host ~[1175] $
false && echo "ok it worked"

<nothing was echoed here, but that is hard to show, hence this explanation>

ansible@c8host ~[1176] $

That was taken from the MIT missing lecture series on youtube

This is the AND operator (&&):

The second command will ONLY run if the first command succeeded. So, here is a better example, good for handling ugly errors:

cat ".bashrc" ;  echo "this is my profile"
cat ".bashrc" && echo "this is my profile"
  • The first choice above will fail with an ugly error if there is no file called .bashrc.
  • The second choice will print "this is my profile" only if there really was a profile. Much better.

5.1.1 Summarizing x && y ==> y only if x true

5.2 or operator || only execute the 2nd part if the first FAILED

The second choice will print the message ONLY if the first command failed. If the first command succeeded, the second command will NOT run.

The above still might have an ugly error, which you can supress by redirecting to null, stderr is &2, i.e. &2 > /dev/null

ansible@c8host ~[1172] $
false || echo "logical OR says false was false"
logical OR says false was false

ansible@c8host ~[1173] $
true || echo "logical OR says false was false"

ansible@c8host ~[1174] $

In bash shell scripts you can do 'cd .. || exit' as a safe way to run a command and if that fails, simply exit the script altogether.

5.2.1 Summarizing x || y ==> y only if x false

5.3 always do the second no matter what, … just concatentate two commands

echo "first" ; echo "second will always print too" The first command completes, before the second command runs.

5.3.1 Summarizing x ; y ==> do x, do y

5.4 &

This is the same as the shell &, i.e. "put this in the background"

sleep 5 ; echo "all done."
sleep 5 & echo "all done."

The first command will print "all done" only after waiting 5 seconds. the second commnd will print "all done" right away, but the script will still take 5 more seconds to finish (less the time it took to echo all done)

5.5 Example:

  1. Inelegant
    if [ "$EDITOR"= "" ]; then
        EDITOR=vim
    fi
    echo $EDITOR
    

    slightly better, but identical is:

    if [ -z "$EDITOR" ]; then
    
  2. Elegant
    [ -Z "$EDITOR" ] && $EDITOR=vim 
    echo $EDITOR
    

    This reads: "if $EDITOR is equal to null, then assign vim to $EDITOR"

  3. -Z "$EDITOR" asks does $EDITOR = null??

5.6 Good code fragment example

add) asktype && askinfo && tryconnect && finalize || delete ;;

This will run each of these commands in order, and continue as long as each of them runs successfully. As soon as one fails, all the others will fail, until you get to the || which will run if any of the previous failed. If they all succeeded, then the delete will NOT run.

Slick and effective.

6 if elif else fi

In bash scripting the conditional statement syntax is:

if <condition>; then<commands>fi
if [conditional]; then
   statements
elif
   statements
else
   statements
fi

The conditionals in the square brackets can be:

! 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.

6.1 conditionals

If they are in [ ] then the statement in the [ ] is evaluated, and if true, the if statement can proceed. This [ ] evaluation is actually a shortcut to the test statement, so the following behave the same:

 if [ $foo -ge 3 ]; then

if test $foo -ge 3; then

'=' does not equal 'eq'

= does a string comparison, while eq is a numeric comparison.

7 special characters

These will obviously impact your comparisons greatly. You must escape them if the actual character is needed, using a \

Common special characters used in Bash

  • ” ” or ‘ ‘ Denotes whitespace. Single quotes preserve literal meaning; double quotes allow substitutions.
  • $ Denotes an expansion (for use with variables, command substitution, arithmetic substitution, etc.)
  • \ Escape character. Used to remove “specialness” from a special character.
  • # Comments. Anything after this character isn’t interpreted.
  • = Assignment
  • [ ] or <double square brackets> Test; evaluates for either true or false
  • ! Negation
  • >>, >, < Input/output redirection
  • | Sends the output of one command to the input of another.
  • * or ? Globs (aka wildcards). ? is a wildcard for a single character.

8 case statements

Case statements take the form:

case <some expression> in

   pattern1)
      echo "since pattern1 is met do these statements"
      ;;

   pattern2)
      echo "since pattern2 is met do these statements"
      ;;

   pattern3)
      echo "since pattern3 is met do these statements"
      ;;

   pattern4)
      echo "since pattern4 is met do these statements"
      ;;

   *)
      echo "all other patterns will match so this always gets done"
      ;;
 esac

From link

Each case statement starts with the case keyword followed by the case expression and the in keyword. The statement ends with the esac keyword.

  • You can use multiple patterns separated by the | operator.
  • The ) operator terminates a pattern list.
  • A pattern can have special characters.
  • A pattern and its associated commands are known as a clause.
  • Each clause must be terminated with ;;.
  • The commands corresponding to the first pattern that matches the expression are executed.
  • It is a common practice to use the wildcard asterisk symbol (*) as a final pattern to define the default case. This pattern will always match.
  • If no pattern is matched the return status is zero. Otherwise, the return status is the exit status of the executed commands

9 Some misc tools to show current state

show memory used vs total

  • free
  • free -h

combining with awk to show 3rd field followed by 2nd field when line starts with "Mem"

  • free -h | awk '/^Mem:/ {print $3 "/" $2}'

breaking down the awk:

  • search for lines starting with Mem:
  • then print the 3rd field,
  • followed by a slash
  • followed by the 2nd field.

show CPU temperature

  • lm_sensors
sensors
sensors | awk '/^temp1/ {print $2}'
  • search for lines beginning with temp1
  • print the 2nd field on that line.

For mac it is "powermetrics'

show cpu processes
ps
ps axc (no minus)


ps axch -o cmd:15,%mem --sort=%mem | head
ps axch -o cmd:15,%cpu --sort=%cpu | head

10 Functions in bash scripts

Bash scripts have several different syntaxes for writing functions, shown here

10.1 functionname() { …. }

For example, putting the following into a file called zintfunc, allows you to source zintfunc which will add functions to your session called, ipaddr, update, clean, helpme, and leave.

# Functions

ipaddr() {

ip addr | egrep '([0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3})' | grep -v 127.0.0.1 | awk '{printf "%-18s%10s\n", $2, $NF}'

}

update() {

echo "Starting update"
sudo dnf update -y
sudo dnf upgrade -y

}

clean() {

echo "Cleaning up"
sudo dnf clean all
echo "This is what you've got installed"
sudo dnf list installed

}

helpme() {

cat << _MYEOF_

So, you need help.  Ok, very simply, this program shows you how to
use functions.  shown above is the who to write the functions, then 
to call, i.e. use, the functions simply use the fucntion name in your script.

update
clean
helpme

_MYEOF_

}

leave() {
echo "=================="
echo "Ok, see you around"
echo "Ok, see you around"
echo "=================="
exit

}

# Main program

echo "Zintis maintenance script, - Oct 2020 \n"

if [ "$1" == "--clean"]; then
   update
   clean
   leave
fi

if [ "$1" == "--help"]; then
   helpme
   leave
fi

if [ -n "$1" ]; then
   echo " Error: needs an argument.  Please run this script with one of:"
   echo "  --help, --clean, --update"
fi

update
leave

11 from Shell Tools and Scripting lecture 2020

Works:

foo=bar
echo $foo

Doesn't work:

foo = bar

because is bash looking for the command 'foo' with two args the first being an '=' the second being 'bar' i.e. don't put the space around the equals sign.

11.1 single vs double quotes:

echo "Value is $foo" Value is bar

echo 'Value is $foo' Value is $foo

  1. "$foo" evaluates $foo
  2. '$foo' is a string $foo

11.2 functions, and $1 is the first arg in bash, $2 is 2nd etc…

Create a file called mcd.sh, and save it with the following:

mcd () {
    mkdir -p "$1"
    cd "$1"
}

running this with source mcd.sh seems to do nothing, but afterwards you can type mcd test and it will create the directory test, as well as cd into it.

11.3 variable names bash assigns to command line arguments

Bash takes command line arguments and assigns them to special variables as follows:

  • $0 is the name of the script
  • $1 is the first arg in bash
  • $2 through to $9 are the second through ninth argument
  • $? is the error from the command
  • $_ is the last arguments from the command

11.4 standard i/o

  • < to read stdin from a file
  • > to write stdout to a file
  • stderr
  • error code
echo "Hello"
Hello
echo $?
0
  • $? is 0 when things run well,
  • $? is 1 when things fail

grep zintis /etc/fstab

  • if zintis is not found, then nothing will appear at the command prompt,
  • but echo $? will be 1
  • thus indicating that grep zintis /etc/fstab failed to find.
  • true will always set $? to 0
  • false will always set $? to 1

11.5 Save output of command into a variable

   foo=$(pwd)

echo "We are in $(pwd)"

11.6 process substitution: <(some-unix-command)

  • cat <(ls) <(ls ..)
  • diff <(ls foo) <(ls bar) will show which files are in one and not the other directory.

This <(somecommand) construct will execute the command and redirect it into stdin

So the cat command above will show a listing of the current directory AND the parent directory.

  1. diff -u file1 file2

    While we are on the topic of diff

    diff can be run in context mode, -c or in unified mode, -u

    Here is a sample of context mode:

    diff -c file1.txt file2.txt                                                                                                              
    *** file1.txt   Thu Jan 11 08:52:37 2018                                                                                                         
    --- file2.txt   Thu Jan 11 08:53:01 2018                                                                                                         
    ***************                                                                                                                                  
    *** 1,4 ****                                                                                                                                     
      cat                                                                                                                                            
    - mv                                                                                                                                             
    - comm                                                                                                                                           
      cp                                                                                                                                             
    --- 1,4 ----                                                                                                                                     
      cat                                                                                                                                            
      cp                                                                                                                                             
    + diff                                                                                                                                           
    + comm 
    

    + indicates a line in the second file that needs to be added to the first file to make them identical

    - indicates a line in the first file that needs to be deleted to make them identical

    Unified mode does NOT display any redundant information. i.e. it is more concise. And here is a sample of unified mode:

    $ cat file1
    one
    two
    three
    four
    five
    
    
    $ cat file2
    one
    two
    two-1
    two-2
    two-3
    three
    Four
    five
    
    
    
    $ diff -u file1 file2
    --- file1       2021-06-19 13:26:26.000000000 -0400
    +++ file2       2021-06-19 13:27:06.000000000 -0400
    @@ -1,5 +1,8 @@
     one
     two
    +two-1
    +two-2
    +two-3
     three
    -four
    +Four
     five
    
    

    The first file is indicated by a - and the 2nd by a +

    @@ - is the line range from the first file, and @@ + is the line range from the 2nd file.

  2. unified diff mode

    When run in unified diff mode with the -u option you get:

    First two lines are

    --- from-file from-file-modification-time
    +++++ to-file to-file-modification-time
    
    --- from-file from-file-modification-time
    +++ to-file to-file-modification-time
    

    Next come one or more hunks of differences with this syntax:

    @@ from-file-line-numbers to-file-line-numbers @@
    line-from-either-file
    line-from-either-file…
    

    If a hunk and its context contain two or more lines, its line numbers look like ‘start,count’. Otherwise only its end line number appears.

    The lines common to both files begin with a space character. The lines that actually differ between the two files have one of the following indicator characters in the left print column:

    ‘+’
    A line was added here to the first file.
    
    ‘-’
    A line was removed here from the first file.
    

    Here is a larger example:

    $ diff -u 1stfile 2ndfile
    --- 1stfile     2020-05-22 11:32:48.000000000 -0400
    +++ 2ndfile     2021-02-02 16:20:15.000000000 -0500
    @@ -1,12 +1,10 @@
     # 
    -# macOS Notice
    +# macOS Notice  This line was changed on the 2nd file.
     #
     # This file is not consulted for DNS hostname resolution, address
     # resolution, or the DNS query routing mechanism used by most
     # processes on this system.
         #
    -# This line is in the first file, but will be removed in 2nd file
    -# This line also will be removed in 2nd file.
     #
     # SEE ALSO
     #   dns-sd(1), scutil(8)
    @@ -17,3 +15,5 @@
     nameserver 208.67.220.220
     You can get so confused that you'll start in to race
     down long wiggled roads at a break-necking pace
    +And        # new line added
    +On and on. # second new line added, hence now we are looking at a block of 5
    

11.7 globbing *.jpg

ls *.jpg is obvious but convert image.{png,jpg} is very useful too as it expands out to: covert image.png impage.jpg

mkdir foo bar
touch {foo,bar}/{a..d}

will expand to: foo/a foo/b foo/c foo/d bar/a bar/b bar/c bar/d

touch bar{,1,2,a,b}

expands to: =bar, bar1, bar2, bara, bar

touch home{1,2}/bin/python/test{1,2,3}.py

expands to:

home1/bin/python/test1.py
home1/bin/python/test2.py
home1/bin/python/test3.py
home2/bin/python/test1.py
home2/bin/python/test3.py
home2/bin/python/test4.py

12 man test

gives you a full listing of all the bash scripting tests available to you. cool cool

13 tldr <command>

will give you a quick example of how to use command

14 Sample bash script

   #!/bin/bash
# a useless program in anything other than showing shell variables
echo "Starting program on $(date)"

echo "Running $0 script with $# arguments with pid $$"

for file in "$@"; do
    grep senecacollege "$file" . /dev/null 2> /dev/null
    # when pattern is NOT found, grep has an exit sttus 1
    # we redirect stdout and stderr to a null retster since we don't care to see
    if [ [ "$?" eq 0 ] ]; then
        echo "senecacollege found in $file"
    fi
done

15 echo vs printf

Taken from stackexchange.com running:

echo "blah" >/dev/udp/localhost/8125 will not send the "blah" command to the server listening on 8125, whereas

printf "blah" >/dev/udp/localhost/8125 will.

echo "blah\n" >/dev/udp/localhost/8125 will as well. (need to confirm)

The difference is that echo sends a newline at the end of its output. There is no way to "send" an EOF.

Both echo and printf are built-in commands (printf is Bash built-in since v2.0.2, 1998). echo always exits with a 0 status, and simply prints arguments followed by an end of line character on the standard output, while printf allows for definition of a formatting string and gives a non-zero exit status code upon failure.

printf has more control over the output format. It can be used to specify the field width to use for item, as well as various formatting choices for numbers (such as what output base to use, whether to print an exponent, whether to print a sign, and how many digits to print after the decimal point). This is done by supplying the format string, that controls how and where to print the other arguments and has the same syntax as C language (%03d, %e, %+d,…). This means that you must escape the percent symbol when not talking about format (%%).

Probably, if you add the newline character that echo uses by default (except when using -n option) you'll get the same effect. so

16 bash debugging

try shellcheck myscript.sh on a bash script for some tips and syntax checking. my CentOS8 Linux system did not have shellcheck, nor did dnf install find it.

(not found on my CentOS system, nor in dnf repos that I have)

16.1 Trick to echo lines and line numbers as script is running

A neat little trick while debugging:

   export PS4="\$LINENO: "
set -xv

The set -xv will print out each line before it is executed, and then the line once the shell interpolates variables, etc. The $PS4 is the prompt used by set -xv. This will print the line number of the shell script as it executes. You'll be able to follow what is going on and where you may have problems.

17 sampling profilers and tracing profilers

sampling profilers take a bigger hit in performanf

every 10ms the program will halt, and look at the stack trace the continue. After enough time, the profiler will give you some statistics to see where most of the time was spent. i.e. which function or method, or line of code.

17.1 tracing profiler

18 tac is a reverse cat

18.1 lsof

list of open files.

19 benchmarking

20 Bash script here documents and here strings

A block of code, that is a form of i/o redirect.

It could be part of a shell script, and it feeds a command list to an interactive program or command line. The here document can be treated as a separate file, or also as multiple line input redirected to a shell script

20.1 here syntax

command << HERESTRING
    text1
    text2
    ...
    textn
HERESTRING

The HERESTRING is often EOF or >>EOF or just EOF The command can be any bash command, for example wc -l which would just show you who many lines are in the HEREDOCUMENT.

For a more useful example, you could cat a here document that lists the arguments of the calling command:

#!/usr/bin/env bash
cat << EOF
   0th argument is: $0
   1st argument is: $1
   2nd argument is: $2
EOF

The run that script with Apples oranges peaches you should get

0th argument is: Apples
1st argument is: oranges
2nd argument is: peaches

An even better example for ftp:

ftp -n << MYFTP 2> /dev/null
       open ftp.acme.com
       user anonymous zintis@cisco.com
       ascii
       prompt
       cd folderofgoodstuff
       mget file1 file2 file3
       bye
MYFTP

20.2 here strings

used for input redirection from text or a variable. The input is included in the same line with single quoatation marks.

wc -w <<< 'Hello World!'

I have also seen here docs starting with the HERESTRING in single quoatation marks, such as

#!/usr/bin/env bash
cat << 'EOF' 
   0th argument is: $0
   1st argument is: $1
   2nd argument is: $2
EOF

20.3 grep string -A3

This will show lines that contain 'string' as well as 3 lines after that line. Try this: grep "if " -A3 ~/bin/python/bin/*.py

Kind of useful, I think.

20.4 grep string -B1

Similarily this shows the line and 1 line before that line as well. grep "elif" -B2 ~/bin/python/bin/*.py

20.5 Input File Seperator, $IFS

By default the $IFS is set to a <space>, i.e. ' ' You can change that in a script, but good idea is to change it back to what it was when you started. We need to save that as $OLDIFS. Like this example, that has input that are comma separated i.e. csv

#!/bin/env bash
OLDIFS=$IFS
IFS=","
... do you thing with inputs that are separated with single tics
IFS=$OLDIFS
#!/bin/env bash
OLDIFS=$IFS
IFS=","

while read user job uid location
# first four fields in a csv file
  do
    echo -e "\e[1;33m$user \
    ==================\e[0m\n\
    Role : \t $job\n\
    ID : \t $uid\n\
    Site : \t $location\n"
  done < $1

IFS=$OLDIFS

note that the echo command above could all have been written on one line but the "\" character simply spans the echo statement across to the next line in the code. It has nothting to do with the newline character, which is \n apart from often coming right after it. You could have written

do
   echo -e "\e[1;33m$user  ==================\e[0m\n Role : \t $job\n ID : \t $uid\n Site : \t $location\n"
 done

And gotten the exact same output from the script.

21 bash script to move files to .Trash

This from a youtube video from Joe Collins :

 #/bin/env bash
 if [ ! -d "$HOME/.Trash" ] ; then
     mkdir "$HOME/.Trash"  
     mkdir "$HOME/.Trash" || exit 1  # a bit more robust
fi

# or even better, replace the above if block with:
[ -d $HOME/.Trash ] || mkdir ~/.Trash || exit 1

# that might not be "better" as it is more obtuse.  i.e. strive for readability over efficiency 

 for FILE in "$@"
     do
         mv "$FILE"" ~/.Trash/
     done

22 Seneca checking scripts

These are good examples of what you can do with a bash script, even on remote servers.

The following is actually a script that runs in three parts.

  1. the main part of the script
  2. a separate script designated with delimiters that runs on remote host 1
  3. a second separate script that runs on remote host 2
#!/bin/bash

# check-assn2.bash

# Author: Murray Saul
# Created: October 7, 2017 Updated: October 20, 2018
# Edited by: Peter Callaghan November 10, 2019

# Purpose: To generate data to be mailed to OPS335 instructor in order
#          to mark OPS335 assignment #2

# Error-checking prior to running shell script
# Check that user is logged on as root

if [ `id -u` -ne 0 ]
then
  echo "You must run this shell script as root" >&2
  exit 1
fi

# Check that mailx application has been installed

if ! which mailx > /dev/null 2> /dev/null 
then
  echo "You need to run \"yum install mailx\" prior to running this shell script" >&2
  exit 1
fi

nameserver=australinea
nameserveraddress=172.28.105.2
mailtransferagent=asia
mailtransferaddress=172.28.105.5
mailsubmissionagent=europe
mailsubmissionaddress=172.28.105.6
sambaserver=southamerica
sambaaddress=172.28.105.8
tld=ops
sld=earth
bld=continents

if ! virsh list | egrep -iqs "($nameserver|$mailtransferagent|$mailsubmissionagent|$sambaserver)"
then
  echo "You need to run your \"$nameserver\", \"$mailtransferagent\", \"$mailsubmissionagent\", and \"$sambaserver\" VMs" >&2
  exit 2
fi


# Prompt for username and Full name

read -p "Please enter YOUR FULL NAME: " fullName

read -p "Please enter YOUR SENECA LOGIN USERNAME: " userID

# Generate Evaluation Report

clear
echo "Your Assignment #2 is being evaluated..."
echo "This make take a few minutes to complete..."


cat <<'PPC' > /tmp/check$mailtransferagent.bash
#!/bin/bash

#Ensure the host name has been set correctly
echo "Hostname:"`hostname`
echo

echo "SELinux status:"`getenforce`
echo

echo "DOMAIN:"`grep -E "^[[:space:]]*DOMAIN=" /etc/sysconfig/network-scripts/ifcfg-*`
echo "DOMAINNAME:"`grep -E "^^[[:space:]]*DOMAINNAME=" /etc/sysconfig/network`
echo "SEARCH:"`grep -E "^^[[:space:]]*search" /etc/resolv.conf`
echo

echo "IP ADDRESS"
ip addr show
echo

echo INTERFACES
for interface in `ls /etc/sysconfig/network-scripts/ifcfg-*`
do
        echo $interface
        cat $interface
        echo
done
echo SECAFRENTI

echo "sshd:"`systemctl is-active sshd.service`
echo "sshd:"`systemctl is-enabled sshd.service`
echo

echo "firewalld:"`systemctl is-active firewalld.service`
echo "firewalld:"`systemctl is-enabled firewalld.service`
echo

echo "iptables:"`systemctl is-active iptables.service`
echo "iptables:"`systemctl is-enabled iptables.service`
echo

echo "postfix:"`systemctl is-active postfix.service`
echo "postfix:"`systemctl is-enabled postfix.service`
echo

echo "RELEASE:"`uname -r`
echo

echo "LAST CHANGE"
last | sed -rne '/still running$/ d' -e '/^reboot[[:space:]]+system boot/ p'
echo

echo UUIDS
for links in `ip link show | grep -E "([a-fA-F0-9]{2}:){5}([a-fA-F0-9]{2})" | sed -rne "s/^.*(([a-fA-F0-9]{2}:){5}[a-fA-F0-9]{2}) brd.*/\1/" -e "/ff:ff:ff:ff:ff:ff/ d" -e "/00:00:00:00:00:00/ d" -e "p"`; do echo $links; done
echo


echo BLKID
blkid | sed -r 's/^.*UUID=\"([a-zA-Z0-9\-]+)\".*$/\1/'
echo

echo IFCFG
sed -n -r -e '/^#?UUID=/ p' -e '/^#?HWADDR=/ p' /etc/sysconfig/network-scripts/ifcfg-* | cut -d'=' -f2
echo

echo IPTABLES
iptables -L -v -n
echo

echo POSTFIX CONFIG
sed -re '/^$/ d' -e '/^[[:space:]]*#/ d' /etc/postfix/main.cf
echo GIFNOC XIFTSOP
echo

yum install -y bind-utils &> /dev/null
echo MAILXCHANGE
dig @172.28.105.2 MX continents.earth.ops. | grep -E "^[^;].*MX"
echo EGNAHCXLIAM
PPC

cat <<'PPC' > /tmp/check$mailsubmissionagent.bash
#!/bin/bash

#Ensure the host name has been set correctly
echo "Hostname:"`hostname`
echo

echo "SELinux status:"`getenforce`
echo

echo "DOMAIN:"`grep -E "^[[:space:]]*DOMAIN=" /etc/sysconfig/network-scripts/ifcfg-*`
echo "DOMAINNAME:"`grep -E "^^[[:space:]]*DOMAINNAME=" /etc/sysconfig/network`
echo "SEARCH:"`grep -E "^^[[:space:]]*search" /etc/resolv.conf`
echo

echo "IP ADDRESS"
ip addr show
echo

echo INTERFACES
for interface in `ls /etc/sysconfig/network-scripts/ifcfg-*`
do
        echo $interface
        cat $interface
        echo
done
echo SECAFRENTI

echo "sshd:"`systemctl is-active sshd.service`
echo "sshd:"`systemctl is-enabled sshd.service`
echo

echo "firewalld:"`systemctl is-active firewalld.service`
echo "firewalld:"`systemctl is-enabled firewalld.service`
echo

echo "iptables:"`systemctl is-active iptables.service`
echo "iptables:"`systemctl is-enabled iptables.service`
echo

echo "postfix:"`systemctl is-active postfix.service`
echo "postfix:"`systemctl is-enabled postfix.service`
echo

echo "dovecot:"`systemctl is-active dovecot.service 2> /dev/null` 
echo "dovecot:"`systemctl is-enabled dovecot.service 2> /dev/null` 
echo

echo "RELEASE:"`uname -r`
echo

echo "LAST CHANGE"
last | sed -rne '/still running$/ d' -e '/^reboot[[:space:]]+system boot/ p'
echo

echo UUIDS
for links in `ip link show | grep -E "([a-fA-F0-9]{2}:){5}([a-fA-F0-9]{2})" | sed -rne "s/^.*(([a-fA-F0-9]{2}:){5}[a-fA-F0-9]{2}) brd.*/\1/" -e "/ff:ff:ff:ff:ff:ff/ d" -e "/00:00:00:00:00:00/ d" -e "p"`; do echo $links; done
echo

echo BLKID
blkid | sed -r 's/^.*UUID=\"([a-zA-Z0-9\-]+)\".*$/\1/'
echo

echo IFCFG
sed -n -r -e '/^#?UUID=/ p' -e '/^#?HWADDR=/ p' /etc/sysconfig/network-scripts/ifcfg-* | cut -d'=' -f2
echo

echo IPTABLES
iptables -L -v -n
echo

echo POSTFIX CONFIG
sed -re '/^$/ d' -e '/^[[:space:]]*#/ d' /etc/postfix/main.cf
echo GIFNOC XIFTSOP
echo

echo DOVECOT CONFIG
sed -re '/^$/ d' -e '/^[[:space:]]*#/ d' /etc/dovecot/dovecot.conf
sed -re '/^$/ d' -e '/^[[:space:]]*#/ d' /etc/dovecot/conf.d/10-{auth,mail,ssl}.conf
echo GIFNOC TOCEVOD
echo

echo ALIASES
echo root:`postalias -q root hash:/etc/aliases`
PPC

cat <<'PPC' > /tmp/check$sambaserver.bash
#!/bin/bash

#Ensure the host name has been set correctly
echo "Hostname:"`hostname`
echo

echo "SELinux status:"`getenforce`
echo

echo "DOMAIN:"`grep -E "^[[:space:]]*DOMAIN=" /etc/sysconfig/network-scripts/ifcfg-*`
echo "DOMAINNAME:"`grep -E "^^[[:space:]]*DOMAINNAME=" /etc/sysconfig/network`
echo "SEARCH:"`grep -E "^^[[:space:]]*search" /etc/resolv.conf`
echo

echo "IP ADDRESS"
ip addr show
echo

echo INTERFACES
for interface in `ls /etc/sysconfig/network-scripts/ifcfg-*`
do
        echo $interface
        cat $interface
        echo
done

echo "sshd:"`systemctl is-active sshd.service`
echo "sshd:"`systemctl is-enabled sshd.service`
echo

echo "smb:"`systemctl is-active smb.service`
echo "smb:"`systemctl is-enabled smb.service`
echo

echo "firewalld:"`systemctl is-active firewalld.service`
echo "firewalld:"`systemctl is-enabled firewalld.service`
echo

echo "iptables:"`systemctl is-active iptables.service`
echo "iptables:"`systemctl is-enabled iptables.service`
echo

echo "RELEASE:"`uname -r`
echo

echo "LAST CHANGE"
last | sed -rne '/still running$/ d' -e '/^reboot[[:space:]]+system boot/ p'
echo

echo UUIDS
for links in `ip link show | grep -E "([a-fA-F0-9]{2}:){5}([a-fA-F0-9]{2})" | sed -rne "s/^.*(([a-fA-F0-9]{2}:){5}[a-fA-F0-9]{2}) brd.*/\1/" -e "/ff:ff:ff:ff:ff:ff/ d" -e "/00:00:00:00:00:00/ d" -e "p"`; do echo $links; done
echo

echo BLKID
blkid | sed -r 's/^.*UUID=\"([a-zA-Z0-9\-]+)\".*$/\1/'
echo

echo IFCFG
sed -n -r -e '/^#?UUID=/ p' -e '/^#?HWADDR=/ p' /etc/sysconfig/network-scripts/ifcfg-* | cut -d'=' -f2
echo

echo IPTABLES
iptables -L -v -n
echo SELBALTI
echo

echo USERS
pdbedit -L
echo SRUSE
echo

echo SAMBA-CONFIG
testparm -s 2>&1
echo GIFNOC-ABMAS
echo

echo SELinuxSEttings
getsebool -a | grep samba
echo sgnittESxuniLES
echo

echo SELinuxContexts
ls -LZR /supercontinents
echo stxetonCxuniLES
PPC

ssh $mailtransferaddress 'bash ' < /tmp/check$mailtransferagent.bash > /tmp/output-$mailtransferagent.txt 2>&1
ssh $mailsubmissionaddress 'bash ' < /tmp/check$mailsubmissionagent.bash > /tmp/output-$mailsubmissionagent.txt 2>&1
ssh $sambaaddress 'bash ' < /tmp/check$sambaserver.bash > /tmp/output-$sambaserver.txt 2>&1

tar -czf a2.$userID.tgz /tmp/output-$mailtransferagent.txt /tmp/output-$mailsubmissionagent.txt /tmp/output-$sambaserver.txt

rm -f  /tmp/check$mailtransferagent.bash /tmp/check$mailsubmissionagent.bash /tmp/check$sambaserver.bash /tmp/output-$mailtransferagent.txt /tmp/output-$mailsubmissionagent.txt /tmp/output-$sambaserver.txt 2> /dev/null

echo "The script created a file called a2.$userID.tgz in the current directory.  Upload that to blackboard for Assignment 2."

23 Paths and basenames in bash scripts

It is often best to work in full path names in scripts, so as to not rely on the current directory where you run your script (i.e. relative paths).

But then you would likely need to get just the filename of a file, without the full path. Bash offers basename and dirname for just this purpose.

for orgfile in /Users/zintis/eg/**/*.org
     do
         base_name=$(basename ${orgfile})
         path_name=${orgfile%/*}
         echo "${orgfile} is the full name"
         echo "${path_name}      is just the path name"
         echo " ---->            ${base_name} is the basename"
     done

24 loop through file contents in a while loop

24.1 method 1

#!/bin/bash
# with input redirection < hello.txt
file="/usr/home/user1/somegoodfile.txt"
while read line
do
   echo "${line}"
   echo $line
done < "${file}"

24.2 method 2

#!/bin/bash
# with cat into a pipe (read into a single variable and print that)
cat hello.txt | while read p
do
    echo $p
done

24.3 method 3

#!/bin/bash
# Use IFF the input field seperator
while IFS=' ' read -r line
do
    echo $line
done < hello.txt

-r prefents the backslash from being interpretted. but method 3 to me should be avoided, because it is obtuse. wtf!??

24.4 method 4

Using file descriptors.

#!/bin/bash
filename='peptides.txt'
exec 4<"$filename"
echo Start
while read -u4 p ; do
    echo "$p"
done

or

cut -d$'\n' -f1 file.cut

"Words of the form $'string' are treated specially. The word expands to string, with backslash-escaped characters replaced as specified by the ANSI C standard."

24.5 same on one line

for word in $(cat peptides.txt); do echo $word; done

24.6 more elaborate using IFS

This is all on one line, but split over three using \ The idea here is to temporarily change the current IFS, input field separator, to a newline, \n, and split the file by new lines. Default would split words I believe.

OLDIFS=$IFS; IFS=$'\n';\
    for line in $(cat peptides.txt); \
    do cmd_a.sh $line; cmd_b.py $line; \
    done > outfile.txt; IFS=$OLDIFS

25 Sample bash sripts

25.1 Backup script from the web:

Check out Jason's script: I got this from jasonmcarman

#!/bin/bash
# Jason Carman; Jasonmcarman@gmail.com
# Created: February 2012
# Updated: August 2013
# Purpose: To automate virtual machine back up and restoration
# Version: 3.1

# script should be run as ./vs [options]
#
# possible options:
#       -b  - Backup
#       -r  - Restore
#       -f  - Restore on Fresh Install of Host OS
#       -o  - Run on one vm, not all

# NOTE: This script should be run as root

# In order to work properly this script requires some initial configuration
# for the variables dpath and spath.  Read further, as all the configuration
# occurs at the top of this script immediately following the comment block.

# Error Codes:
#       1 - User provided invalid option
#       2 - User selected both -b and -r
#       3 - User selected neither -b or -r
#       4 - User didn't provide an option
#       5 - Destination path not found
#       6 - Source path not found
#       7 - User provided a vm name but no action to perform (-b or -r)
#       8 - User selected -o but didn't provide a vm name

#########################################################################
## Variable Configuration - change these values to reflect your system ##
#########################################################################

# these path names should reflect your own directory structure and backup storage location
###### IMPORTANT! ######
# For this to work properly the directories contained in the variable must exist.  If you're doing a restore on a fresh install of the host, create the directory contained in dpath and copy the files there before you begin the process.

# destination path:
dpath='/home/jmcarman/Backups'

# source path:
spath='/var/lib/libvirt/images'

# Array to store virtual machine names, to add more virtual machines to this copy the same format.  To change these names to reflect your system, keep the single quotes and change the content inbetween.
#
# format vms=(nameofmachine1 nameofmachine2 nameofmachine3 nameofmachine4) ect....
vms=(centos1 centos2 centos3)

# Variable for log text file for easy reference
logfile=virtualsafety-log.txt

#########################################################################################
## Changes beyond this point not recommended unless you really know what you're doing, ##
## as results may be unpredictable.                                                    ##
#########################################################################################

function backup() {
        # change directory to where the virtual machines are stored as files
        cd $spath

        # use virsh dumpxml to create back ups of the virtual machines
        virsh dumpxml $vm >$dpath/$vm.xml

        # gzip images and store them in back up directory, run in the background
        echo "Creating backup of $vm in $dpath"
        touch $dpath/$vm.backup.gz
        $(gzip <$spath/$vm >$dpath/$vm.backup.gz)&
        progress
        logMsg="$logMsg $vm,"
}

function restore() {
        cd $spath
        echo "Restoring $vm"
        $(gunzip <$dpath/$vm.backup.gz >$spath/$vm)&
        progress
        logMsg="$logMsg $vm,"
        cp $spath/$vm.xml $dpath/$vm.xml
        virsh define $vm.xml
}

function logfile () {
        # check to see if the destination and sourch path exist
        if [ ! -e "$dpath" ]; then
                echo "Destination path: "$dpath" does not exist, please reconfigure your script and run again"
                exit 5
        fi
        if [ ! -e "$spath" ]; then
                echo "Source path: "$dpath" does not exist, please reconfigure your script and run again"
                exit 6
        fi

        logcheck=$(ls $dpath | grep $logfile)
        if [[ "$logcheck" == "" ]]; then # log file doesn't exist, create it
                echo "VIRTUAL SAFETY LOG" > $dpath/$logfile
                echo "FOR: $HOSTNAME" >> $dpath/$logfile
                echo "Backup Source Location: $spath" >> $dpath/$logfile
                echo "Backup Destination Location: $dpath" >> $dpath/$logifle
                echo "Start: " $(date | cut -d" " -f2,3,6) >> $dpath/$logfile
                echo "End: " >> $dpath/$logfile
                echo "--------------------------------------" >> $dpath/$logfile
        else # update the end date
                sed -i "s/End\:.*/End\:\ $(date | cut -d' ' -f2,3,6)/" $dpath/$logfile
        fi
}

function progress() {
        # $! is most recent running process ID, check running processes to see if the backround gzip has completed.  While it's running, print a "#" on the screen every 3 seconds.     
        while [[ $(ps | grep $!) != "" ]]; do 
                echo -n '#'
                sleep 3
        done
        echo "" # To keep messages and prompts aligned properly
}

function completion() {
        if [[ $b == 1 ]]; then
                action=Backup
        elif [[ $r == 1 ]]; then
                action=Restoration
        fi
        # tell the user the backup process has finished
        echo "Automated $action Complete, process logged to $dpath/$logfile"
        # append log time and date to a file for use in future documentation, add a blank line after for easier readability
        logMsg="$logMsg created with virtualsafety script on"
        echo "$logMsg" $(date) >> $dpath/$logfile
        echo "" >> $dpath/$logfile
}

while getopts brfo: options
do
        case $options in
                b) b=1
                        ;;
                r) r=1
                        ;;
                f) f=1
                        ;;
                o) o=1;vm=$OPTARG
                        ;;
                \?) echo "You have selected an invalid option, please use one of the following: -b for backup, -r for restore and if using -r please use -f if this restore is being done on a fresh install of the host"
                exit 1
                        ;;
        esac
done

if [[ $# == 0 ]]; then
        echo "./vs must be called with at least one argument, -b to back up -r to restore, -f with -r if doing the restoration on a fresh install of the host"
        exit 4
fi

if [[ "$b" == 1 && "$r" == 1 ]]; then
        echo "You must select either backup (-b) or restore (-r), not both"
        exit 2
elif [[ "$b" == 0 && "$r" == 0 ]]; then
        echo "You must select either backup (-b) or restore (-r)"
        exit 3
elif [[ "$b" == 0 || "$r" == 0 && "$o" == 1 ]]; then
        echo "You must select either backup (-b) or restore (-r) with -o"
        exit 7
elif [[ "$o" == 1 && $vm == "" ]]; then
        echo "You must provide the name of a virtual machine with -o"
        exit 8
fi

logfile $dpath $logfile

if [[ "$b" == 1 ]]; then
        # tell the user the back up is in progress
        echo "Begining automated back up..."
        logMsg="Backups of"

        # call the backup function in a for loop refferencing the array set above, passing it the parameters stored in the array $vms and variables $dpath, $spath.
        if [[ "$o" == 1 ]]; then
                backup $vm $dpath $spath
        else
                for vm in "${vms[@]}"; do
                        backup $vm $dpath $spath
                done
                # Call the completion function to log the message to the appropriate file
                completion $b $dpath $logfile $logMsg
        fi      
fi

if [[ "$r" == 1 ]]; then
        logMsg="Restoration of"
        if [[ "$f" == 1 ]]; then
                echo "Installing virtual machine manager and updating the host"
                # Install virtualization software and update the host
                yum -y groupinstall "Virtualization"
                yum -y update

                # Start the virtualization service and enable it to start on boot up
                systemctl start libvirtd
                systemctl enable libvirtd
        else
                echo "Begining automated restoration..."
                if [[ "$o" == 1 ]]; then # restore only one machine
                        restore $vm $dpath $spath
                else
                        # call the restore function in a for loop refferencing the array set above, passing it the parameters stored in the array $vms and variables $dpath, $spath.
                        for vm in "${vms[@]}"; do
                                restore $vm $dpath $spath
                        done
                        # Call the completion function to log the message to the appropriate file
                        completion $r $dpath $logfile $logMsg
                fi              
        fi
fi
vs
Displaying vs.

25.2 Allow an ip address to ssh through iptables script

#!/bin/bash

# ask for an ip address, then add a temporary rule to iptables to allow that
# address to ssh to this host
# temporary because after a reboot, these new addtions will no longer be there
# i.e. they are not permanent.

echo "Current MYSSH iptables are:"
sudo iptables -L MYSSH -n --line-numbers

echo " "
echo " -----------------------------"

echo " Where are you trying to access me from? "
echo " Enter the ip address in the form a.b.c.d or a.b.c.0/24 or any other CIDR block form: "
read myip

echo " "
echo " Do you want to add $myip to your iptables.  (y/n) ? "

read proceedornot

if [ $proceedornot = "y" ]; then
        sudo iptables -I MYSSH 4 -s $myip -p tcp --dport 1963 -j ACCEPT
        # this iptables command inserts (I) into the MYSSH list, position 4
        # the ip address $myip to be accepted on port 1963
        echo " -----------------------------"
        sudo iptables -L MYSSH -n --line-numbers
fi

echo $proceedornot



25.3 Home