START OF WEEK 4 of Linux

<<< FILE TIMESTAMP >>>
  -The up to date file is: /usr/courses/cps393/dwoit/courseNotes/
  -If you are viewing your own copy, check it is up to date
  -If you are viewing from a link in the Course Outline, be aware it may be outdated.



<< ARRAYS >>

 -Indexed Arrays:
  students[0]=Bob
  students[1]=Ha
  students[2]=Sue

  #could declare, but not required
  declare -a students
  declare -a students[3] #but subscript is ignored

  echo ${students[1]}    #Ha
  echo ${students[*]}    #Bob Ha Sue. Can use @ instead of *
  echo ${#students[*]}   #3  is updated if add more

  #or can set arrays in one step:
  students=(Bob Ha Sue)

   X=0
   echo ${students[X]}   #or ${students[$X]}



 -Associative Arrays:
  #index can be any string
  declare -A myA
  myA[dog]=woof
  myA[cat]=meow
  myA[mouse]=squeek
  echo ${myA[cat]}   #meow
  echo ${myA[*]}     #woof meow squeek
  echo ${#myA[*]}    #3


  #can simulate 2-dimensional arrays by associative array:
  declare -A ary    #or typeset -A ary
  ary[0,0]=0
  ary[2,1]=2
  ary[1,1]=1
  echo ${ary[2,1]}  #2
  echo ${ary[*]}    #1 2 0   - just prints what exists
  echo ${ary[@]}    #same as *

-search for "Arrays" in man bash



HMWK:  Use arrays to write a shell program called revarg.sh that will
         print out its command line args in reverse order.




<<< FOREGROUND/BACKGROUND PROCESSING, SUSPENDING, KILLING >>>

execute commands in background with  &

/home/dwoit> find / -name firefox  >find.out 2>/dev/null  &
/home/dwoit>            # free to do other work
/home/dwoit> ls -R / 2>/dev/null | grep x >/tmp/deleteMe &

/home/dwoit> ps         # lists processes, ids
                    ... PID  ... CMD
                    ... 332498 ... find
                    ... 341483 ... ls
                    ... 351269 ... grep
                    ... 403398 ... ps
/home/dwoit> jobs 
                        [1]   Running                 find ... &
                        [2]   Running                 ls   ... &

/home/dwoit> fg %1      # now in foreground--no prompt until done
                ^z      # suspends foreground process (ctrl-z)
/home/dwoit>            # prompt back--free to do other work
/home/dwoit> jobs
                        [1]   Stopped                 find ...  
                        [2]   Running                 ls   ... &

/home/dwoit> bg %1      # starts find again in background
/home/dwoit> kill %1    or
/home/dwoit> kill 332498     # find cmd is killed  (^c if fg process)
/home/dwoit> kill -9 332498  # might need guaranteed kill

Note: for more detail, try these (where "userid" is your userid):
      "ps a" or "ps -l" or "ps -u userid" 

      e.g., ps a
      lists all processes of dwoit on this machine, not just
      in one shell (the shell from which the ps command was run).
      ps output may differ on different installations


<<< COMMAND HISTORY EDITING FOR BASH >>>

history         lists last commands (each is numbered)
                (could also use fc -l)
!!              re-execute last command issued
!cmd            re-execute last command that started with string "cmd"
!-n             re-execute current command minus n 
!n              re-execute command number n in history
^str1^str2^     re-execute last cmd but substitute str2 for str1

e.g.
/home/dwoit> ls
                lab1.jav lab2.jav lab1.c393 lab2.c393
/home/dwoit> !!
                ls
                lab1.jav lab2.jav lab1.c393 lab2.c393
/home/dwoit> find . -name "*.jav" 
                lab1.jav lab2.jav
/home/dwoit> ^jav^c393^
                find . -name "*.c393"
                lab1.c393 lab2.c393
/home/dwoit> history
                . . .
                8  ls
                9  ls
                10 find . -name "*.jav" 
                11 find . -name "*.c393" 
                12 history
/home/dwoit> !10
                find . -name "*.jav"
                lab1.jav lab2.jav

set -o vi       makes command line editing use vi
set -o emacs    "                            " emacs

if set -o vi, then
esc-k           brings back last command for editing (using vi)
                repeated "k"s move back in history, "j" move forward
up-arrow        also works instead of k (and down-arrow instead of j)
                (as long as do esc-k to start)
Then can edit line using vim commands 


if set -o emacs, then
up-arrow        brings back last command for editing (using emacs)
                repeated "up-arrows"s move back in history, "down-arrows" move forward
Then can edit line using emacs commands



HMWK: When the "find" command tries to look in a directory you do not 
have permission to read, it prints an error message on stderr 
(try this out to see what happens). If you redirect stderr to 
stdout, then "find" will print both on stdout and you can pipe all
the output. Use find, pipes, grep and wc to print the *number* of 
directories that "find" cannot read in your filesystem. 
--
Do the same thing, but have the number written into a file called 
num.no.read (using redirection).  
--

Repeat the previous question (output to file) but for the entire 
filesystem (i.e., start your "find" search at root)--but run the 
command in the background, or you will have to wait a long time 
for it to finish (and/or num.no.read might get so large you run out
of disk space). 
If you want to logout before your find command is done, kill it first.
Delete your num.no.read file.




<<< FUNCTIONS >>>


Can use functions to modularize shell programs
Can also use them in shell as you use any shell program
Useful inside .bash_profile or .bashrc or .profile


Syntax:
    function_name ( ) { 
      command_list
    }

eg.

   tired of typing "ls -ld" so make a function called ll

   ll () {
      /bin/ls -ld
      }

Can put in .bashrc, etc., or enter interactively in shell:
   /home/dwoit> ll () {
   Linux> ls -ld
   Linux> }
   /home/dwoit>
   /home/dwoit> ll
   drwx--x--x 122 dwoit dwoit 36864 Sep 30 13:36 .

<< ARGUMENTS >>

Same as for shell programs:  $1 $2, ...,  $*, etc


<< FUNCTIONS IN SHELL PROGRAMS >>

#!/bin/bash
#Source: showLastLines.sh
 lastLine () {
   echo -e "Last line of $1 is:\t\c "
   tail -1 $1
 }
 lastLine $1
 lastLine $2
 lastLine $3


#!/bin/bash
#Source: xy
abc () {
  echo -e "in abc.\t My \$2 is: $2"
}
echo -e "in xy.\t My \$2 is: $2"
abc x "y z" w
abc a b c d
echo -e "in xy.\t My \$2 is: $2"


<< LIKE COMPLEX "ALIAS" >>

Functions executed before shell commands 
Thus, to make "echo" different, make a function (type into
shell each session, or put in .bash_profile, .bashrc, .profile, 
etc. and re-login or source )


To define "echo" to always be "echo -e" could do alias, or
       echo () { 
         /bin/echo -e "$@" 
       }

NOTE1: "$@" is all arguments, with whitespace preserved properly
NOTE2: do not do:
       echo () { 
         echo -e "$@" 
       }
     It is a recursive call to the newly-defined echo function
     (Infinite loop)   ctrl-c to kill it

NOTE3: do:           /bin/echo -e "$@"   
       instead of:   /bin/echo -e
     the latter ALWAYS does only "echo -e" even if you supply arguments
     e.g., echo x y z
     does "echo -e" NOT "echo -e x y z"

NOTE4: resolves commands in this order: alias, function, built-in, PATH
       Thus, do not make a function with same name as an existing 
       alias because the function will never run.
       unset the alias first (unalias), then run function. 

Once echo function defined, will always run when you type echo.
To get the regular bash echo, run it directly, or use command e.g.,
   /home/dwoit> echo "hi\nthere"
   hi
   there
   /home/dwoit> /bin/echo "hi\nthere"
   hi\nthere
   /home/dwoit> command echo "hi\nthere"  #suppresses function lookup
   hi\nthere


----------------------------------------------------
OPTIONAL
     "$@" is expansion of all function`s arguments, with whitespace
     properly preserved, so this works: echo "a b" c
     If want compatibility with the sh shell, use ${1+"$@"} 
     ${1+"$@"} means: if $1 unset or null replace ${1+"$@"} with null;
                      otherwise replace ${1+"$@"} with the expansion of $@

     If a command starts with slash, that named file is executed, i.e.,
     it DOES not try to resolve as: alias, function, built-in, PATH
OPTIONAL
----------------------------------------------------



<< CHECK IF SOMETHING IS A FUNCTION OR NOT >>

  type function_name
  e.g.

  /home/dwoit> ll () {
  Linux > /bin/ls -ld
  Linux > }
  /home/dwoit>
 
  /home/dwoit> type ll
  ll is a function
  ll ()
  {
      /bin/ls -ld
  }

<< DELETING A FUNCTION >>
 
  unset -f function_name  #unset fn unsets variable fn, not function fn
                          #but, if no variable fn defined then
                          #unsets function fn
  /home/dwoit> unset -f ll ; type ll
  -bash: type: ll: not found

  /home/dwoit> type echo  #supposing it is this...
  echo is a function
  echo ()
  {
      /bin/echo -e "$@"
  }

  /home/dwoit> unset -f echo ; type echo
  echo is a shell builtin


<< CHECK WHAT FUNCTIONS ARE DEFINED >>

  declare -f              #lists all functions defined in this shell
                          #typeset same as declare


<< FUNCTIONS ARE RUN IN CURRENT/CALLING SHELL >>

Functions are executed in current shell. Thus,
  function can permanently change the environment

e.g. in shell:
/home/dwoit> ccc () {
>              cd /usr/courses/
>            }
/home/dwoit> ccc
/usr/courses>


Compare running ccc above with running program mycd.sh
#!/bin/bash
#Source mycd.sh
cd /usr/courses/

/home/dwoit> mycd.sh
/home/dwoit> 

Why did mycd NOT change the environment (directory)?
Because SHELL PROGRAMS RUN IN A SUB-SHELL.  


<< VARIABLES AND FUNCTION SCOPE >>

A shell program cannot access variables of its calling 
shell because shell programs run in a SUBSHELL. (but it
can access them if the calling shell exported them.)

A shell program can never modify variables of its
calling shell (even if they were exported).

/home/dwoit> cat myprog.sh
#!/bin/bash
x="def"
/home/dwoit> x="abc"
/home/dwoit> myprog
/home/dwoit> echo ${x}
abc
/home/dwoit> 

A function runs in the current (calling) shell, therefore, it
 -CAN access/modify variables in current shell
 -CAN change the environment of its calling shell

/home/dwoit> myfn () {
> x="def"
> }
/home/dwoit>
/home/dwoit> x="abc"
/home/dwoit> myfn
/home/dwoit> echo ${x}
def
/home/dwoit>


#!/bin/bash
#source: afuncPgm.sh
afunc () {
   x="def"
}
x="abc"
afunc
echo ${x}    #prints def

When a function is defined within a shell program, the function`s 
calling shell is the shell program.

/home/dwoit> x="yyy" ; echo ${x}    
yyy
/home/dwoit> afuncPgm.sh
def                 
/home/dwoit> echo ${x}              
yyy


<< PIPING OR CAPTURING A FUNCTION`S RESULT >>

Functions called as above work as shown above (change calling
environment). HOWEVER, if pipe function`s result, or capture 
its result in a variable, then function is run in a sub-shell, 
and, although it has access to global variables and can change 
them in the sub-shell, the change is lost when the function finishes.


#!/bin/bash
#Source: afuncPgmX.sh
#Same functionality as afuncPgm.sh (cat just sends stdin to stdout so
#changes nothing)

afunc () {
   x="def"
   }
x="abc"
afunc | cat  #piping makes afunc run in subshell
echo ${x}    #prints abc



<< LOCAL FUNCTION VARIABLES >>

Create variables local to a function, using local or typeset or declare:
  local x=30     #or declare or typeset 
x is now local to the function. 
Otherwise x is set permanently in current shell

/home/dwoit> x="abc"
/home/dwoit> afuncPgm.sh
def
/home/dwoit> afuncPgmLocal.sh
abc




<< ACCESSING CALLER`S POSITIONAL PARAMETERS ($1, $2, ETC.) >>

 Remember, if a shell program calls a function, all the shell
 program`s variables ARE available to the function. 
 However, the shell program`s own arguments $1, $2, ... are NOT.

 Why? Because function`s own arguments override the shell pgm`s.

 A shell program has arguments $1 $2 ...

 If it calls a function, then WITHIN the function, $1 $2 ... are 
 those of the function (the shell program`s original $1 $2 ... are 
 not available within the function, but they are still available
 to the shell program after the function returns)
 Even if the shell pgm calls the function with no arguments, the
 shell program`s $1 $2 ... are still unavailable in the function
 ($1, $2, ... are null within the function.)
 
 If need to access shell pgm`s arguments from within a function:
   -could store $1, $2, ... in other variables before calling function
   -typically, use an array for this

#!/bin/bash
#Source: floc.sh
#try running this as:  floc.sh a b c
ff () {
  echo "in ff: my \$2 is: $2"
  echo "in ff: my caller's \$2 is ${A[2]}"
}
echo in floc.sh before call to ff: \$2 is:  $2
A=($0 "$@")      #initialize an array A to be all the positional params
ff xxx yyy zzz
echo in floc after call to ff: \$2 is:  $2


Note1: the quotes on $@ are required so that whitespace is preserved:
       e.g., > floc a "b c" d    #works correctly
Note2: included $0 in array so that ff`s $1, $2, ... line up with floc`s 
       (since array indicies start at 0). Could use any string. 


HMWK:
Write a shell program named paTest.sh which expects any number of command
line arguments (CLAs). paTest.sh prompts the user for a list of argument
numbers. It then passes this list to a function named printargs which
prints the values of those argument numbers of paTest.sh. e.g.,
> paTest.sh one two three four five six seven eight
Enter the argument numbers you would like to print: 3 4 7
three
four
seven
>



END OF WEEK 4 (UNIX)


May do u4Lab now