10/1/2015course material created by d. woit 1 cps 393 introduction to unix and c start of week 5...
TRANSCRIPT
04/19/23 Course material created by D. Woit
1
CPS 393Introduction to Unix and C
START OF WEEK 5 (UNIX)
04/19/23 Course material created by D. Woit 1
04/19/23 Course material created by D. Woit
2
Testing & Debugging Shell Pgms • use shell parameters -x or -v to debug interactively• bash -v pgm
– displays every line of pgm just before executing it
• bash -x pgm
– like -v but substitutes values of variables in each
• Before we proceed let us recall:• `command(s)` means that command(s) will be executed in a
sub-shell and resulting value will be put in place of command(s)
04/19/23 Course material created by D. Woit 2
04/19/23 Course material created by D. Woit
3
Testing cont.• Example:• #!/bin/bash• #Source: findtrunc truncates input to 8 characters &• #looks it up in file gfile• # a msg is printed to indicate if found/not found• # use first method on linux, second on unix (why? bug in linux bash--• # the read variable only valid within a subshell• # that runs each part of a pipe in a different process, so vars do not• # get into the process running the "read“• # third method always works, but no help if want to use read to separate• #by whitespace• # fourth method is valid on both linux/unix is to put a ( before the read• # and a ) at the end of the program to keep everything IN the subshell•
04/19/23 Course material created by D. Woit 3
04/19/23 Course material created by D. Woit
4
Testing cont. code for findtrunc • #first method:• echo "$1" | cut -c1-8 >tmp• read trunc <tmp• #second method:• echo "$1" | cut -c1-8 | read trunc• #third method• trunc=`echo "$1" | cut -c1-8` #no spaces between trunc and =
• if [ "`grep $trunc gfile`" ]• then echo Found• else echo not found• fi
04/19/23 Course material created by D. Woit 4
04/19/23 Course material created by D. Woit
5
Testing continued, code for findtrunc
• try to run: findtrunc abcdefghijkl
– prints Found #if abcdefgh in gfile
• try to run: findtrunc– no output, just hangs there , then press ^C , ^D to stop it
• prints not found
• What's the problem with findtrunc? Why does it hang?
• Use -x to find out why:
04/19/23 Course material created by D. Woit 5
04/19/23 Course material created by D. Woit
6
Testing continued, code for findtrunc
• bash -x findtrunc
• + echo ''• + cut -c1-8
• + read trunc
• ++ grep gfile <---- hangs, wanting to grep string gfile• from stdin. If we type ^D signals • end of stdin, giving...• +'[' '' ']' <---- since grep printed nothing on stdout• + echo not found <---- since [ ] (null string) is considered false• not found • + test -e tmp• + rm tmp <---- terminates
04/19/23 Course material created by D. Woit 6
04/19/23 Course material created by D. Woit
7
Testing continued, code for findtrunc
• with bash –e can tell where hanging
• In this case, we forgot the argument to findtrunc, so it was null• we can fix this by adding an :
– enclosing if [ $1 ] (if $1 not null)
– or something similar, such as if [ $# -ge 1 ]
• Note: Could have used ( ) to solve problem of reading from pipe in above program, as in:
• echo "$1" | cut -c1-8 | ( read trunc• if [ "`grep $trunc gfile`" ]• then echo Found• else echo not found• fi• )• Recall ( commands ) means that they will be executed in sub-shell
04/19/23 Course material created by D. Woit 7
04/19/23 Course material created by D. Woit
8
Shift• Shift command is used in while loops to process arguments
• #!/bin/bash• #Source: shiftex • #pgm to print its args.• while [ "$1" ] #<--- stops when $1= "" (null)• do• echo $1• shift #<--- moves $2 to $1, $3 to $2 etc.• done• exit 0
04/19/23 Course material created by D. Woit 8
04/19/23 Course material created by D. Woit
9
Xargs• used to perform cmd repetitively on a group of things from
stdin but can be used after redirection• find . -name "*" | xargs grep bash
– #prints LINES in files * that contain the string bash
• find . -type d -empty | xargs rmdir– #remove empty directories from tree rooted in current dir
• ls | xargs -I{} cp {} {}.bak
– makes backup copy of all files in current dir. (no spaces between {} please)
– if xx is a dir get cp xx xx.bak -- error, but will just print msg for each dir and continue on with files
• e.g., what does this print on stdout?
• (echo a b c ; echo d e ; echo f) | xargs -I{} echo :{}:
04/19/23 Course material created by D. Woit 9
04/19/23 Course material created by D. Woit
10
HMWK• 1. write a shell program called clean that removes all files
called "core" from your directory structure, and also removes all files that have size 0 (0 bytes) from your dir structure. That's your WHOLE dir structure, not just the current dir.
• (note: "find" can find files that are zero bytes long)
04/19/23 Course material created by D. Woit 10
04/19/23 Course material created by D. Woit
11
HMWK• 1.write a shell program called avgs which will read lines from
• stdin such as the following (where the dots indicate more lines• of the same kind:• SID LNAME I T1/20 T2/30• 92876035 WANG S 15 26• 95908659 CHIANG R 10 29 • . . . . . . . . . .• 91234987 MYRTH R 15 16• • Your shell program will print out a line such as:• Average of T1/20 is 17 and of T2/30 is 24
– where 17 and 24 are the averages of the last 2 columns respectively. Note that your program must keep a total and count for each of the last 2 columns, and must not include data from the first line in the totals and counts.
04/19/23 Course material created by D. Woit 11
04/19/23 Course material created by D. Woit
12
HMWK• 2. Modify script avgs so that the "title" line, e.g.,
• SID LNAME I T1/20 T2/30
above, could be located at ANY LINE of stdin (ie., not necessarily the FIRST line, as above.)
• 3. Modify avgs so that the highest and lowest marks are printed for each of T1 and T2, as well as averages.
• Your program should produce output such as the following:• T1/20: Average: 17 Hightest: 19 Lowest: 1• T2/30: Average: 24 Hightest: 29 Lowest: 9
04/19/23 Course material created by D. Woit 12
04/19/23 Course material created by D. Woit
13
HMWK • 4. Modify avgs so that the output above is in PERCENT, e.g.,
• T1/20: Average: 85% Hightest: 95% Lowest: 5% • T2/30: Average: 80% Hightest: 96% Lowest: 3%
• If variable x contains "T1/20" you could use an echo piped to
• a cut, and assign the result to another variable, or you• could use ${x#*/} which will match pattern "*/" against the • beginning of the contents of x, delete the shortest part that• matches, and return the rest. Thus typeset -i y=${x#*/} would• assign 20 to y. Note that pattern */ means anything followed by• a "/"
04/19/23 Course material created by D. Woit 13
04/19/23 Course material created by D. Woit
14
HMWK• 5. Write a shell program called findext which will list directories under a
given path that contain files/dirs having the given extensions.• Program findext takes at least 2 command line arguments. Its usage is:• findext path ext1 ext2 ... where "path" is the path to the start of the
directory structure you wish to search, and ext1, ext2, etc are extensions.• Program findext will print out the directories under path which contain
files/dirs that end in .ext1 .ext2 etc• Your program does not need to work properly if the user passes glob
constructs or special chars as arguments.• For example:• findext /home/dwoit/courses c scm• will list all directories under /home/dwoit/courses that contain files/dirs
named *.c and/or *.scm•
04/19/23 Course material created by D. Woit 14
04/19/23 Course material created by D. Woit
15
HMWK 5 cont.• If findext is sent less than 2 command line args, it should print on stderr
• Usage: findext path arg1 arg2
• and exit with exitcode 1• (the word findext above should be printed using $0)• If it is passed 2 or more arguments, it should print a list of dirs in which
files/dirs of the form *.ext1 *.ext2 etc reside. Then it should exit with exitcode 0.
• Note that only the DIRECTORY names are printed. Therefore you will• have to chop off the trailing file/dir name (the part after the last "/")• You can do this with ${var%/*} which says to match pattern "/*" (slash• followed by anything) at the END of variable var; and delete the shortest• part that matches and return the rest. You will also find utilities• sort and uniq useful.
04/19/23 Course material created by D. Woit 15
04/19/23 Course material created by D. Woit
16
Built in commands• We saw already: echo, exit, pwd, shopt, break, cd, set, test,
read, typeset, etc. • Some others : eval
• v1="cmd1 | cmd2" #make a string "cmd1 | cmd2"
• v2="cmd3 | cmd4" #make a string• eval "$v1 | cmd5 | $v2" #is really: • cmd1 | cmd2 | cmd5 | cmd3 | cmd 4 #pipes• However if we typed: • $v1 | cmd5 | $v2
– # error: the "|" in v1 not interpreted as pipe
04/19/23 Course material created by D. Woit 16
04/19/23 Course material created by D. Woit
17
Eval cont.• Example for eval:• v1="cat table | grep 2"• v2="grep 0 | more "• $v1 | $v2 #some errors like:
– grep: |: No such file or directory
– grep: more: No such file or directory
• Correct invocation is: eval “$v1 | $v2”– Output is: 9 10 11 12
• eval has 2 passes:– 1) makes substitutions– 2) result evaluated i.e, meta chars etc. interpreted properly
04/19/23 Course material created by D. Woit 17
04/19/23 Course material created by D. Woit
18
Eval example• #!/bin/bash• #Source: printXth• #Expects a bunch of command line arguments.• #Prompts user and reads an integer (call it X)• #then it prints the Xth command line argument• echo -n "enter an integer: "• read X• the_arg='$'${X}• eval echo "command line arg number ${X} is: ${the_arg}"• echo "command line arg number ${X} is: ${the_arg}"
– #note the diff
04/19/23 Course material created by D. Woit 18
04/19/23 Course material created by D. Woit
19
Eval example• #!/bin/bash• #Source: for11• #one way to make command line arg "abc de" work:• typeset -i i=1• while [ $i -le $# ]• do• a='$'$i• eval "echo $a" # or b=`eval "echo $a"` ; echo $b #see for12
• i=i+1• done
04/19/23 Course material created by D. Woit 19
04/19/23 Course material created by D. Woit
20
HMWK• Use eval to write a shell program called revargs which will
print out its command line args in reverse order. • Write a shell program that doesn't use eval to print its clas in• reverse order.
04/19/23 Course material created by D. Woit 20
04/19/23 Course material created by D. Woit
21
Shell commands
• most shell cmds (i.e. not built in ones) in "bins"
• /bin, /usr/bin , /usr/local/bin , even ~/bin• shell searches directories to find cmds.• searches those in "PATH"• to see path echo $PATH• /usr/local/bin:/usr/bin:/bin:/usr/local/games:/usr/games:/usr/courses/bin/i686::
• to add (or change) default path, reset PATH in .profile
• PATH=${PATH}:${HOME}/bin• export PATH• - Adds /home/dwoit/bin to end of current path (for me)• (I put all shell my personal pgms in /home/dwoit/bin)• -export makes PATH global• (so all subsequent processes can use it.)
04/19/23 Course material created by D. Woit 21
04/19/23 Course material created by D. Woit
22
Problem with path• Problem occurs if we have two different commands with
same name,– Shell will execute first one it finds in path
• i.e. if you wrote a "rm" cmd to use instead of /bin/rm– you must put your bin /dwoit/bin before /bin in PATH, or /bin/rm
will be always executed.
• (or can make it a function or alias, which are always done first)
04/19/23 Course material created by D. Woit 22
04/19/23 Course material created by D. Woit
23
Functions• Can use functions to modularize shell programs• Can also use them in shell as you use any shell program• Often placed in a .profile• Syntax: function_name ( ) { command_list; }• For example: if we are tired of doing "ls -ld" so make a
function called ll• ll () {• ls -ld• }• Can also enter interactively in shell:• /home/dwoit> ll () {• Linux> ls -ld• Linux> }
04/19/23 Course material created by D. Woit 23
04/19/23 Course material created by D. Woit
24
Arguments for functions• Just like for shell programs: $1 $2, ... $* etc• Functions in shell programs
• In a shell program called xy
• #!/bin/bash• #Source: xy• abc () {• echo "in function abc. My \$2 is: $2"• }• echo in xy...calling abc x "y z" w• abc x "y z" w• echo in xy...calling abc a b c d• abc a b c d
04/19/23 Course material created by D. Woit 24
04/19/23 Course material created by D. Woit
25
Like complex "alias" • Functions are done before shell commands• Thus, to make "vi" different make a function (in .profile)• vi () {• for i # each parameter to vi which can file or options +-• do• x=`echo $i | cut -c1`• if [ "$x" != "-" -a "$x" != "+" ]• then• cp $i $i.bak• fi• done• /usr/bin/vi "$@“ # “$@” is equivalent to “$1” “$2” …• }
04/19/23 Course material created by D. Woit 25
04/19/23 Course material created by D. Woit
26
Functions cont.• To define "ls" to always be "ls -l" could do alias, or• ls () {• /usr/bin/ls -l• }
– But this ls always shows whole contents of the directory.– There is no option to show only selected files and/or directories
• Also NOTE1: do not do:• ls () {• ls -l• }• Why? It is a *recursive* call to the newly-defined ls *function*• (Infinite loop)
04/19/23 Course material created by D. Woit 26
04/19/23 Course material created by D. Woit
27
Functions cont.
• NOTE2: in ls() function above we should really do:
• /usr/bin/ls -l ${1+"$@"}
• instead of: /usr/bin/ls -l• Why? the latter ALWAYS does "ls -l" even if you supply
arguments• e.g., ls x y z• will NOT do ls -l ONLY for x y z, it does it for *all* files• The ${1+"$@"} means: if $1 is unset or null replace
${1+"$@"} with null;• otherwise replace ${1+"$@"} with the expansion of $@• (which are all the function's args)
04/19/23 Course material created by D. Woit 27
04/19/23 Course material created by D. Woit
28
Determining if something is a function or not • type function_name• e.g. once we defined ll()• /home/dwoit> type ll• ll is a function• ll ()• {• ls -ld• }• /home/dwoit> unset ll• /home/dwoit> type ll• -bash: type: ll: not found
• for previous example ls• /home/dwoit> type ls• ls is a function• ls ()• {• /bin/ls ${1+"$@"}• }• • /home/dwoit> unset ls• /home/dwoit> type ls• ls is hashed (/bin/ls)
04/19/23 Course material created by D. Woit 28
04/19/23 Course material created by D. Woit
29
Determining what functions are defined • typeset -f
• #lists all functions defined this session• << Deleting a function >>• unset -f function_name #unset f unsets *variable* f, not
function f• #however, if no variable defined then unset f will unset
function f• << Function scope>>• Functions executed in current shell, not subshell (as normal
shell pgm)• Thus: if function changes variables, current dir, etc – change
of variables is PERMANENT
04/19/23 Course material created by D. Woit 29
04/19/23 Course material created by D. Woit
30
Function Scope
• e.g.• /home/dwoit> ff () {• > x="def"• > }• /home/dwoit> x="abc";
echo $x• abc• /home/dwoit> ff• /home/dwoit> echo $x• def
• From a shell program:• e.g.,• #!/bin/bash• ff () {• x="def"• }• x="abc"• ff• echo $x #will print def
04/19/23 Course material created by D. Woit 30
04/19/23 Course material created by D. Woit
31
Variables in a function• We can make LOCAL variables in a function by using
typeset:• typeset x=30
– makes x local to the function.
• Otherwise x is set permanently in current shell
04/19/23 Course material created by D. Woit 31
04/19/23 Course material created by D. Woit
32
Getting at a shell function's positional parameters from within a function it calls:
• A shell program has parameters $1 $2 ...• If it calls a function, then WITHIN the function, params. $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 program calls the function with no arguments, the shell program's params. $1 $2 ... are still unavailable in the function (because they're all null within the function.)
• If we need to access shell pgm's args from within a function:– Then store $1, $2, ... in arguments before calling function
04/19/23 Course material created by D. Woit 32
04/19/23 Course material created by D. Woit
33
Passing the shell program’s args to function: • #!/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 ff: \$2 is: $2• A=($0 $*) #initialize an array A to be all the positional params
• ff xxx yyy zzz• echo in floc after ff: \$2 is: $2
04/19/23 Course material created by D. Woit 33