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 #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 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) might need guaranteed kill: kill -9 332498 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 esc-k (and down-arrow to move forward) Then can edit line using vim commands (as long as at least 1 esc-k) 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 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 done 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) 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--function will never run. unset the alias first, then define 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 #!/bin/bash #Source mycd cd /usr/courses/ /home/dwoit> mycd /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/modify variables of its calling shell because shell programs run in a SUBSHELL: /home/dwoit> cat myprog #!/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 #!/bin/bash #source: afuncPgm afunc () { x="def" } x="abc" afunc echo ${x} #prints def /home/dwoit> x="yyy" ; echo ${x} yyy /home/dwoit> afuncPgm 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 afunc () { x="def" } x="abc" afunc | tr -d "a" echo ${x} #prints abc << LOCAL FUNCTION VARIABLES >> Create variables local to a function, using local or typeset: local x=30 #or typeset x=30 x is now local to the function. Otherwise x is set permanently in current shell /home/dwoit> x="abc" /home/dwoit> afuncPgm def /home/dwoit> afuncPgmLocal 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 #try running this as: floc a b c ff () { echo "in ff: my \$2 is: $2" echo "in ff: my caller's \$2 is ${A[2]}" } echo in floc 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). Any arbitrary string achieves same thing. HMWK: Write a shell program named paTest which expects any number of command line arguments (CLAs). paTest 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. e.g., > paTest 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