Search     or:     and:
 LINUX 
 Language 
 Kernel 
 Package 
 Book 
 Test 
 OS 
 Forum 
iakovlev.org

/proc

The /proc directory is actually a pseudo-filesystem. The files in /proc mirror currently running system and kernel processes and contain information and statistics about them.

bash$ cat /proc/devices
 Character devices:
    1 mem
    2 pty
    3 ttyp
    4 ttyS
    5 cua
    7 vcs
   10 misc
   14 sound
   29 fb
   36 netlink
  128 ptm
  136 pts
  162 raw
  254 pcmcia
 
  Block devices:
    1 ramdisk
    2 fd
    3 ide0
    9 md
 
 
 
 bash$ cat /proc/interrupts
            CPU0       
    0:      84505          XT-PIC  timer
    1:       3375          XT-PIC  keyboard
    2:          0          XT-PIC  cascade
    5:          1          XT-PIC  soundblaster
    8:          1          XT-PIC  rtc
   12:       4231          XT-PIC  PS/2 Mouse
   14:     109373          XT-PIC  ide0
  NMI:          0 
  ERR:          0
 
 
 bash$ cat /proc/partitions
 major minor  #blocks  name     rio rmerge rsect ruse wio wmerge wsect wuse running use aveq
 
     3     0    3007872 hda 4472 22260 114520 94240 3551 18703 50384 549710 0 111550 644030
     3     1      52416 hda1 27 395 844 960 4 2 14 180 0 800 1140
     3     2          1 hda2 0 0 0 0 0 0 0 0 0 0 0
     3     4     165280 hda4 10 0 20 210 0 0 0 0 0 210 210
     ...
 
 
 
 bash$ cat /proc/loadavg
 0.13 0.42 0.27 2/44 1119
 
 
 
 bash$ cat /proc/apm
 1.16 1.2 0x03 0x01 0xff 0x80 -1% -1 ?
          

Shell scripts may extract data from certain of the files in /proc. [1]

FS=iso                       # ISO filesystem support in kernel?
 
 grep $FS /proc/filesystems   # iso9660

kernel_version=$( awk '{ print $3 }' /proc/version )

CPU=$( awk '/model name/ {print $4}' < /proc/cpuinfo )
 
 if [ $CPU = Pentium ]
 then
   run_some_commands
   ...
 else
   run_different_commands
   ...
 fi

devfile="/proc/bus/usb/devices"
 USB1="Spd=12"
 USB2="Spd=480"
 
 
 bus_speed=$(grep Spd $devfile | awk '{print $9}')
 
 if [ "$bus_speed" = "$USB1" ]
 then
   echo "USB 1.1 port found."
   # Do something appropriate for USB 1.1.
 fi

The /proc directory contains subdirectories with unusual numerical names. Every one of these names maps to the process ID of a currently running process. Within each of these subdirectories, there are a number of files that hold useful information about the corresponding process. The stat and status files keep running statistics on the process, the cmdline file holds the command-line arguments the process was invoked with, and the exe file is a symbolic link to the complete path name of the invoking process. There are a few more such files, but these seem to be the most interesting from a scripting standpoint.

Example 27-2. Finding the process associated with a PID

#!/bin/bash
 # pid-identifier.sh: Gives complete path name to process associated with pid.
 
 ARGNO=1  # Number of arguments the script expects.
 E_WRONGARGS=65
 E_BADPID=66
 E_NOSUCHPROCESS=67
 E_NOPERMISSION=68
 PROCFILE=exe
 
 if [ $# -ne $ARGNO ]
 then
   echo "Usage: `basename $0` PID-number" >&2  # Error message >stderr.
   exit $E_WRONGARGS
 fi  
 
 pidno=$( ps ax | grep $1 | awk '{ print $1 }' | grep $1 )
 # Checks for pid in "ps" listing, field #1.
 # Then makes sure it is the actual process, not the process invoked by this script.
 # The last "grep $1" filters out this possibility.
 if [ -z "$pidno" ]  # If, after all the filtering, the result is a zero-length string,
 then                # no running process corresponds to the pid given.
   echo "No such process running."
   exit $E_NOSUCHPROCESS
 fi  
 
 # Alternatively:
 #   if ! ps $1 > /dev/null 2>&1
 #   then                # no running process corresponds to the pid given.
 #     echo "No such process running."
 #     exit $E_NOSUCHPROCESS
 #    fi
 
 # To simplify the entire process, use "pidof".
 
 
 if [ ! -r "/proc/$1/$PROCFILE" ]  # Check for read permission.
 then
   echo "Process $1 running, but..."
   echo "Can't get read permission on /proc/$1/$PROCFILE."
   exit $E_NOPERMISSION  # Ordinary user can't access some files in /proc.
 fi  
 
 # The last two tests may be replaced by:
 #    if ! kill -0 $1 > /dev/null 2>&1 # '0' is not a signal, but
                                       # this will test whether it is possible
                                       # to send a signal to the process.
 #    then echo "PID doesn't exist or you're not its owner" >&2
 #    exit $E_BADPID
 #    fi
 
 
 
 exe_file=$( ls -l /proc/$1 | grep "exe" | awk '{ print $11 }' )
 # Or       exe_file=$( ls -l /proc/$1/exe | awk '{print $11}' )
 #
 # /proc/pid-number/exe is a symbolic link
 # to the complete path name of the invoking process.
 
 if [ -e "$exe_file" ]  # If /proc/pid-number/exe exists...
 then                 # the corresponding process exists.
   echo "Process #$1 invoked by $exe_file."
 else
   echo "No such process running."
 fi  
 
 
 # This elaborate script can *almost* be replaced by
 # ps ax | grep $1 | awk '{ print $5 }'
 # However, this will not work...
 # because the fifth field of 'ps' is argv[0] of the process,
 # not the executable file path.
 #
 # However, either of the following would work.
 #       find /proc/$1/exe -printf '%l\n'
 #       lsof -aFn -p $1 -d txt | sed -ne 's/^n//p'
 
 # Additional commentary by Stephane Chazelas.
 
 exit 0

Example 27-3. On-line connect status

#!/bin/bash
 
 PROCNAME=pppd        # ppp daemon
 PROCFILENAME=status  # Where to look.
 NOTCONNECTED=65
 INTERVAL=2           # Update every 2 seconds.
 
 pidno=$( ps ax | grep -v "ps ax" | grep -v grep | grep $PROCNAME | awk '{ print $1 }' )
 # Finding the process number of 'pppd', the 'ppp daemon'.
 # Have to filter out the process lines generated by the search itself.
 #
 #  However, as Oleg Philon points out,
 #+ this could have been considerably simplified by using "pidof".
 #  pidno=$( pidof $PROCNAME )
 #
 #  Moral of the story:
 #+ When a command sequence gets too complex, look for a shortcut.
 
 
 if [ -z "$pidno" ]   # If no pid, then process is not running.
 then
   echo "Not connected."
   exit $NOTCONNECTED
 else
   echo "Connected."; echo
 fi
 
 while [ true ]       # Endless loop, script can be improved here.
 do
 
   if [ ! -e "/proc/$pidno/$PROCFILENAME" ]
   # While process running, then "status" file exists.
   then
     echo "Disconnected."
     exit $NOTCONNECTED
   fi
 
 netstat -s | grep "packets received"  # Get some connect statistics.
 netstat -s | grep "packets delivered"
 
 
   sleep $INTERVAL
   echo; echo
 
 done
 
 exit 0
 
 # As it stands, this script must be terminated with a Control-C.
 
 #    Exercises:
 #    ---------
 #    Improve the script so it exits on a "q" keystroke.
 #    Make the script more user-friendly in other ways.

Warning

In general, it is dangerous to write to the files in /proc, as this can corrupt the filesystem or crash the machine.

Notes

[1]

Certain system commands, such as procinfo, free, vmstat, lsdev, and uptime do this as well.

Debugging

Debugging is twice as hard as writing the code in the first place. Therefore, if you write the code as cleverly as possible, you are, by definition, not smart enough to debug it.

Brian Kernighan

The Bash shell contains no debugger, nor even any debugging-specific commands or constructs. [1] Syntax errors or outright typos in the script generate cryptic error messages that are often of no help in debugging a non-functional script.

Example 29-1. A buggy script

#!/bin/bash
 # ex74.sh
 
 # This is a buggy script.
 # Where, oh where is the error?
 
 a=37
 
 if [$a -gt 27 ]
 then
   echo $a
 fi  
 
 exit 0

Output from script:
./ex74.sh: [37: command not found
What's wrong with the above script (hint: after the if)?

Example 29-2. Missing keyword

#!/bin/bash
 # missing-keyword.sh: What error message will this generate?
 
 for a in 1 2 3
 do
   echo "$a"
 # done     # Required keyword 'done' commented out in line 7.
 
 exit 0  

Output from script:
missing-keyword.sh: line 10: syntax error: unexpected end of file
 	
Note that the error message does not necessarily reference the line in which the error occurs, but the line where the Bash interpreter finally becomes aware of the error.

Error messages may disregard comment lines in a script when reporting the line number of a syntax error.

What if the script executes, but does not work as expected? This is the all too familiar logic error.

Example 29-3. test24, another buggy script

#!/bin/bash
 
 #  This script is supposed to delete all filenames in current directory
 #+ containing embedded spaces.
 #  It doesn't work.
 #  Why not?
 
 
 badname=`ls | grep ' '`
 
 # Try this:
 # echo "$badname"
 
 rm "$badname"
 
 exit 0

Try to find out what's wrong with Example 29-3 by uncommenting the echo "$badname" line. Echo statements are useful for seeing whether what you expect is actually what you get.

In this particular case, rm "$badname" will not give the desired results because $badname should not be quoted. Placing it in quotes ensures that rm has only one argument (it will match only one filename). A partial fix is to remove to quotes from $badname and to reset $IFS to contain only a newline, IFS=$'\n'. However, there are simpler ways of going about it.
# Correct methods of deleting filenames containing spaces.
 rm *\ *
 rm *" "*
 rm *' '*
 # Thank you. S.C.

Summarizing the symptoms of a buggy script,

  1. It bombs with a "syntax error" message, or

  2. It runs, but does not work as expected (logic error).

  3. It runs, works as expected, but has nasty side effects (logic bomb).

Tools for debugging non-working scripts include

  1. echo statements at critical points in the script to trace the variables, and otherwise give a snapshot of what is going on.

    Tip

    Even better is an echo that echoes only when debug is on.
    ### debecho (debug-echo), by Stefano Falsetto ###
     ### Will echo passed parameters only if DEBUG is set to a value. ###
     debecho () {
       if [ ! -z "$DEBUG" ]; then
          echo "$1" >&2
          #         ^^^ to stderr
       fi
     }
     
     DEBUG=on
     Whatever=whatnot
     debecho $Whatever   # whatnot
     
     DEBUG=
     Whatever=notwhat
     debecho $Whatever   # (Will not echo.)

  2. using the tee filter to check processes or data flows at critical points.

  3. setting option flags -n -v -x

    sh -n scriptname checks for syntax errors without actually running the script. This is the equivalent of inserting set -n or set -o noexec into the script. Note that certain types of syntax errors can slip past this check.

    sh -v scriptname echoes each command before executing it. This is the equivalent of inserting set -v or set -o verbose in the script.

    The -n and -v flags work well together. sh -nv scriptname gives a verbose syntax check.

    sh -x scriptname echoes the result each command, but in an abbreviated manner. This is the equivalent of inserting set -x or set -o xtrace in the script.

    Inserting set -u or set -o nounset in the script runs it, but gives an unbound variable error message at each attempt to use an undeclared variable.

  4. Using an "assert" function to test a variable or condition at critical points in a script. (This is an idea borrowed from C.)

    Example 29-4. Testing a condition with an "assert"

    #!/bin/bash
     # assert.sh
     
     assert ()                 #  If condition false,
     {                         #+ exit from script with error message.
       E_PARAM_ERR=98
       E_ASSERT_FAILED=99
     
     
       if [ -z "$2" ]          # Not enough parameters passed.
       then
         return $E_PARAM_ERR   # No damage done.
       fi
     
       lineno=$2
     
       if [ ! $1 ] 
       then
         echo "Assertion failed:  \"$1\""
         echo "File \"$0\", line $lineno"
         exit $E_ASSERT_FAILED
       # else
       #   return
       #   and continue executing script.
       fi  
     }    
     
     
     a=5
     b=4
     condition="$a -lt $b"     # Error message and exit from script.
                               #  Try setting "condition" to something else,
                               #+ and see what happens.
     
     assert "$condition" $LINENO
     # The remainder of the script executes only if the "assert" does not fail.
     
     
     # Some commands.
     # ...
     echo "This statement echoes only if the \"assert\" does not fail."
     # ...
     # Some more commands.
     
     exit 0
  5. trapping at exit.

    The exit command in a script triggers a signal 0, terminating the process, that is, the script itself. [2] It is often useful to trap the exit, forcing a "printout" of variables, for example. The trap must be the first command in the script.

Trapping signals

trap

Specifies an action on receipt of a signal; also useful for debugging.

Note

A signal is simply a message sent to a process, either by the kernel or another process, telling it to take some specified action (usually to terminate). For example, hitting a Control-C, sends a user interrupt, an INT signal, to a running program.

trap '' 2
 # Ignore interrupt 2 (Control-C), with no action specified. 
 
 trap 'echo "Control-C disabled."' 2
 # Message when Control-C pressed.

Example 29-5. Trapping at exit

#!/bin/bash
 # Hunting variables with a trap.
 
 trap 'echo Variable Listing --- a = $a  b = $b' EXIT
 #  EXIT is the name of the signal generated upon exit from a script.
 #
 #  The command specified by the "trap" doesn't execute until
 #+ the appropriate signal is sent.
 
 echo "This prints before the \"trap\" --"
 echo "even though the script sees the \"trap\" first."
 echo
 
 a=39
 
 b=36
 
 exit 0
 #  Note that commenting out the 'exit' command makes no difference,
 #+ since the script exits in any case after running out of commands.

Example 29-6. Cleaning up after Control-C

#!/bin/bash
 # logon.sh: A quick 'n dirty script to check whether you are on-line yet.
 
 umask 177  # Make sure temp files are not world readable.
 
 
 TRUE=1
 LOGFILE=/var/log/messages
 #  Note that $LOGFILE must be readable
 #+ (as root, chmod 644 /var/log/messages).
 TEMPFILE=temp.$$
 #  Create a "unique" temp file name, using process id of the script.
 #     Using 'mktemp' is an alternative.
 #     For example:
 #     TEMPFILE=`mktemp temp.XXXXXX`
 KEYWORD=address
 #  At logon, the line "remote IP address xxx.xxx.xxx.xxx"
 #                      appended to /var/log/messages.
 ONLINE=22
 USER_INTERRUPT=13
 CHECK_LINES=100
 #  How many lines in log file to check.
 
 trap 'rm -f $TEMPFILE; exit $USER_INTERRUPT' TERM INT
 #  Cleans up the temp file if script interrupted by control-c.
 
 echo
 
 while [ $TRUE ]  #Endless loop.
 do
   tail -$CHECK_LINES $LOGFILE> $TEMPFILE
   #  Saves last 100 lines of system log file as temp file.
   #  Necessary, since newer kernels generate many log messages at log on.
   search=`grep $KEYWORD $TEMPFILE`
   #  Checks for presence of the "IP address" phrase,
   #+ indicating a successful logon.
 
   if [ ! -z "$search" ] #  Quotes necessary because of possible spaces.
   then
      echo "On-line"
      rm -f $TEMPFILE    #  Clean up temp file.
      exit $ONLINE
   else
      echo -n "."        #  The -n option to echo suppresses newline,
                         #+ so you get continuous rows of dots.
   fi
 
   sleep 1  
 done  
 
 
 #  Note: if you change the KEYWORD variable to "Exit",
 #+ this script can be used while on-line
 #+ to check for an unexpected logoff.
 
 # Exercise: Change the script, per the above note,
 #           and prettify it.
 
 exit 0
 
 
 # Nick Drage suggests an alternate method:
 
 while true
   do ifconfig ppp0 | grep UP 1> /dev/null && echo "connected" && exit 0
   echo -n "."   # Prints dots (.....) until connected.
   sleep 2
 done
 
 # Problem: Hitting Control-C to terminate this process may be insufficient.
 #+         (Dots may keep on echoing.)
 # Exercise: Fix this.
 
 
 
 # Stephane Chazelas has yet another alternative:
 
 CHECK_INTERVAL=1
 
 while ! tail -1 "$LOGFILE" | grep -q "$KEYWORD"
 do echo -n .
    sleep $CHECK_INTERVAL
 done
 echo "On-line"
 
 # Exercise: Discuss the relative strengths and weaknesses
 #           of each of these various approaches.

Note

The DEBUG argument to trap causes a specified action to execute after every command in a script. This permits tracing variables, for example.

Example 29-7. Tracing a variable

#!/bin/bash
 
 trap 'echo "VARIABLE-TRACE> \$variable = \"$variable\""' DEBUG
 # Echoes the value of $variable after every command.
 
 variable=29
 
 echo "Just initialized \"\$variable\" to $variable."
 
 let "variable *= 3"
 echo "Just multiplied \"\$variable\" by 3."
 
 exit $?
 
 #  The "trap 'command1 . . . command2 . . .' DEBUG" construct is
 #+ more appropriate in the context of a complex script,
 #+ where placing multiple "echo $variable" statements might be
 #+ clumsy and time-consuming.
 
 # Thanks, Stephane Chazelas for the pointer.
 
 
 Output of script:
 
 VARIABLE-TRACE> $variable = ""
 VARIABLE-TRACE> $variable = "29"
 Just initialized "$variable" to 29.
 VARIABLE-TRACE> $variable = "29"
 VARIABLE-TRACE> $variable = "87"
 Just multiplied "$variable" by 3.
 VARIABLE-TRACE> $variable = "87"

Of course, the trap command has other uses aside from debugging.

Example 29-8. Running multiple processes (on an SMP box)

#!/bin/bash
 # multiple-processes.sh: Run multiple processes on an SMP box.
 
 # Script written by Vernia Damiano.
 # Used with permission.
 
 #  Must call script with at least one integer parameter
 #+ (number of concurrent processes).
 #  All other parameters are passed through to the processes started.
 
 
 INDICE=8        # Total number of process to start
 TEMPO=5         # Maximum sleep time per process
 E_BADARGS=65    # No arg(s) passed to script.
 
 if [ $# -eq 0 ] # Check for at least one argument passed to script.
 then
   echo "Usage: `basename $0` number_of_processes [passed params]"
   exit $E_BADARGS
 fi
 
 NUMPROC=$1              # Number of concurrent process
 shift
 PARAMETRI=( "$@" )      # Parameters of each process
 
 function avvia() {
 	local temp
 	local index
 	temp=$RANDOM
 	index=$1
 	shift
 	let "temp %= $TEMPO"
 	let "temp += 1"
 	echo "Starting $index Time:$temp" "$@"
 	sleep ${temp}
 	echo "Ending $index"
 	kill -s SIGRTMIN $$
 }
 
 function parti() {
 	if [ $INDICE -gt 0 ] ; then
 		avvia $INDICE "${PARAMETRI[@]}" &
 		let "INDICE--"
 	else
 		trap : SIGRTMIN
 	fi
 }
 
 trap parti SIGRTMIN
 
 while [ "$NUMPROC" -gt 0 ]; do
 	parti;
 	let "NUMPROC--"
 done
 
 wait
 trap - SIGRTMIN
 
 exit $?
 
 : <<SCRIPT_AUTHOR_COMMENTS
 I had the need to run a program, with specified options, on a number of
 different files, using a SMP machine. So I thought [I'd] keep running
 a specified number of processes and start a new one each time . . . one
 of these terminates.
 
 The "wait" instruction does not help, since it waits for a given process
 or *all* process started in background. So I wrote [this] bash script
 that can do the job, using the "trap" instruction.
   --Vernia Damiano
 SCRIPT_AUTHOR_COMMENTS

Note

trap '' SIGNAL (two adjacent apostrophes) disables SIGNAL for the remainder of the script. trap SIGNAL restores the functioning of SIGNAL once more. This is useful to protect a critical portion of a script from an undesirable interrupt.

	trap '' 2  # Signal 2 is Control-C, now disabled.
 	command
 	command
 	command
 	trap 2     # Reenables Control-C
 	

Gotchas

Turandot: Gli enigmi sono tre, la morte una!

Caleph: No, no! Gli enigmi sono tre, una la vita!

Puccini

Assigning reserved words or characters to variable names.
case=value0       # Causes problems.
 23skidoo=value1   # Also problems.
 # Variable names starting with a digit are reserved by the shell.
 # Try _23skidoo=value1. Starting variables with an underscore is o.k.
 
 # However . . .   using just the underscore will not work.
 _=25
 echo $_           # $_ is a special variable set to last arg of last command.
 
 xyz((!*=value2    # Causes severe problems.
 # As of version 3 of Bash, periods are not allowed within variable names.

Using a hyphen or other reserved characters in a variable name (or function name).
var-1=23
 # Use 'var_1' instead.
 
 function-whatever ()   # Error
 # Use 'function_whatever ()' instead.
 
  
 # As of version 3 of Bash, periods are not allowed within function names.
 function.whatever ()   # Error
 # Use 'functionWhatever ()' instead.

Using the same name for a variable and a function. This can make a script difficult to understand.
do_something ()
 {
   echo "This function does something with \"$1\"."
 }
 
 do_something=do_something
 
 do_something do_something
 
 # All this is legal, but highly confusing.

Using whitespace inappropriately. In contrast to other programming languages, Bash can be quite finicky about whitespace.
var1 = 23   # 'var1=23' is correct.
 # On line above, Bash attempts to execute command "var1"
 # with the arguments "=" and "23".
 	
 let c = $a - $b   # 'let c=$a-$b' or 'let "c = $a - $b"' are correct.
 
 if [ $a -le 5]    # if [ $a -le 5 ]   is correct.
 # if [ "$a" -le 5 ]   is even better.
 # [[ $a -le 5 ]] also works.

Assuming uninitialized variables (variables before a value is assigned to them) are "zeroed out". An uninitialized variable has a value of "null", not zero.
#!/bin/bash
 
 echo "uninitialized_var = $uninitialized_var"
 # uninitialized_var =

Mixing up = and -eq in a test. Remember, = is for comparing literal variables and -eq for integers.
if [ "$a" = 273 ]      # Is $a an integer or string?
 if [ "$a" -eq 273 ]    # If $a is an integer.
 
 # Sometimes you can mix up -eq and = without adverse consequences.
 # However . . .
 
 
 a=273.0   # Not an integer.
 	   
 if [ "$a" = 273 ]
 then
   echo "Comparison works."
 else  
   echo "Comparison does not work."
 fi    # Comparison does not work.
 
 # Same with   a=" 273"  and a="0273".
 
 
 # Likewise, problems trying to use "-eq" with non-integer values.
 	   
 if [ "$a" -eq 273.0 ]
 then
   echo "a = $a"
 fi  # Aborts with an error message.  
 # test.sh: [: 273.0: integer expression expected

Misusing string comparison operators.

Example 31-1. Numerical and string comparison are not equivalent

#!/bin/bash
 # bad-op.sh: Trying to use a string comparison on integers.
 
 echo
 number=1
 
 # The following "while loop" has two errors:
 #+ one blatant, and the other subtle.
 
 while [ "$number" < 5 ]    # Wrong! Should be:  while [ "$number" -lt 5 ]
 do
   echo -n "$number "
   let "number += 1"
 done  
 #  Attempt to run this bombs with the error message:
 #+ bad-op.sh: line 10: 5: No such file or directory
 #  Within single brackets, "<" must be escaped,
 #+ and even then, it's still wrong for comparing integers.
 
 
 echo "---------------------"
 
 
 while [ "$number" \< 5 ]    #  1 2 3 4
 do                          #
   echo -n "$number "        #  This *seems to work, but . . .
   let "number += 1"         #+ it  actually does an ASCII comparison,
 done                        #+ rather than a numerical one.
 
 echo; echo "---------------------"
 
 # This can cause problems. For example:
 
 lesser=5
 greater=105
 
 if [ "$greater" \< "$lesser" ]
 then
   echo "$greater is less than $lesser"
 fi                          # 105 is less than 5
 #  In fact, "105" actually is less than "5"
 #+ in a string comparison (ASCII sort order).
 
 echo
 
 exit 0

Sometimes variables within "test" brackets ([ ]) need to be quoted (double quotes). Failure to do so may cause unexpected behavior. See Example 7-6, Example 16-5, and Example 9-6.

Commands issued from a script may fail to execute because the script owner lacks execute permission for them. If a user cannot invoke a command from the command line, then putting it into a script will likewise fail. Try changing the attributes of the command in question, perhaps even setting the suid bit (as root, of course).

Attempting to use - as a redirection operator (which it is not) will usually result in an unpleasant surprise.
command1 2> - | command2  # Trying to redirect error output of command1 into a pipe...
 #    ...will not work.	
 
 command1 2>& - | command2  # Also futile.
 
 Thanks, S.C.

Using Bash version 2+ functionality may cause a bailout with error messages. Older Linux machines may have version 1.XX of Bash as the default installation.
#!/bin/bash
 
 minimum_version=2
 # Since Chet Ramey is constantly adding features to Bash,
 # you may set $minimum_version to 2.XX, or whatever is appropriate.
 E_BAD_VERSION=80
 
 if [ "$BASH_VERSION" \< "$minimum_version" ]
 then
   echo "This script works only with Bash, version $minimum or greater."
   echo "Upgrade strongly recommended."
   exit $E_BAD_VERSION
 fi
 
 ...

Using Bash-specific functionality in a Bourne shell script (#!/bin/sh) on a non-Linux machine may cause unexpected behavior. A Linux system usually aliases sh to bash, but this does not necessarily hold true for a generic UNIX machine.

Using undocumented features in Bash turns out to be a dangerous practice. In previous releases of this book there were several scripts that depended on the "feature" that, although the maximum value of an exit or return value was 255, that limit did not apply to negative integers. Unfortunately, in version 2.05b and later, that loophole disappeared. See Example 23-9.

A script with DOS-type newlines (\r\n) will fail to execute, since #!/bin/bash\r\n is not recognized, not the same as the expected #!/bin/bash\n. The fix is to convert the script to UNIX-style newlines.
#!/bin/bash
 
 echo "Here"
 
 unix2dos $0    # Script changes itself to DOS format.
 chmod 755 $0   # Change back to execute permission.
                # The 'unix2dos' command removes execute permission.
 
 ./$0           # Script tries to run itself again.
                # But it won't work as a DOS file.
 
 echo "There"
 
 exit 0

A shell script headed by #!/bin/sh will not run in full Bash-compatibility mode. Some Bash-specific functions might be disabled. Scripts that need complete access to all the Bash-specific extensions should start with #!/bin/bash.

Putting whitespace in front of the terminating limit string of a here document will cause unexpected behavior in a script.

A script may not export variables back to its parent process, the shell, or to the environment. Just as we learned in biology, a child process can inherit from a parent, but not vice versa.
WHATEVER=/home/bozo
 export WHATEVER
 exit 0
bash$ echo $WHATEVER
 
 bash$ 
Sure enough, back at the command prompt, $WHATEVER remains unset.

Setting and manipulating variables in a subshell, then attempting to use those same variables outside the scope of the subshell will result an unpleasant surprise.

Example 31-2. Subshell Pitfalls

#!/bin/bash
 # Pitfalls of variables in a subshell.
 
 outer_variable=outer
 echo
 echo "outer_variable = $outer_variable"
 echo
 
 (
 # Begin subshell
 
 echo "outer_variable inside subshell = $outer_variable"
 inner_variable=inner  # Set
 echo "inner_variable inside subshell = $inner_variable"
 outer_variable=inner  # Will value change globally?
 echo "outer_variable inside subshell = $outer_variable"
 
 # Will 'exporting' make a difference?
 #    export inner_variable
 #    export outer_variable
 # Try it and see.
 
 # End subshell
 )
 
 echo
 echo "inner_variable outside subshell = $inner_variable"  # Unset.
 echo "outer_variable outside subshell = $outer_variable"  # Unchanged.
 echo
 
 exit 0
 
 # What happens if you uncomment lines 19 and 20?
 # Does it make a difference?

Piping echo output to a read may produce unexpected results. In this scenario, the read acts as if it were running in a subshell. Instead, use the set command (as in Example 11-16).

Example 31-3. Piping the output of echo to a read

#!/bin/bash
 #  badread.sh:
 #  Attempting to use 'echo and 'read'
 #+ to assign variables non-interactively.
 
 a=aaa
 b=bbb
 c=ccc
 
 echo "one two three" | read a b c
 # Try to reassign a, b, and c.
 
 echo
 echo "a = $a"  # a = aaa
 echo "b = $b"  # b = bbb
 echo "c = $c"  # c = ccc
 # Reassignment failed.
 
 # ------------------------------
 
 # Try the following alternative.
 
 var=`echo "one two three"`
 set -- $var
 a=$1; b=$2; c=$3
 
 echo "-------"
 echo "a = $a"  # a = one
 echo "b = $b"  # b = two
 echo "c = $c"  # c = three 
 # Reassignment succeeded.
 
 # ------------------------------
 
 #  Note also that an echo to a 'read' works within a subshell.
 #  However, the value of the variable changes *only* within the subshell.
 
 a=aaa          # Starting all over again.
 b=bbb
 c=ccc
 
 echo; echo
 echo "one two three" | ( read a b c;
 echo "Inside subshell: "; echo "a = $a"; echo "b = $b"; echo "c = $c" )
 # a = one
 # b = two
 # c = three
 echo "-----------------"
 echo "Outside subshell: "
 echo "a = $a"  # a = aaa
 echo "b = $b"  # b = bbb
 echo "c = $c"  # c = ccc
 echo
 
 exit 0

In fact, as Anthony Richardson points out, piping to any loop can cause a similar problem.

# Loop piping troubles.
 #  This example by Anthony Richardson,
 #+ with addendum by Wilbert Berendsen.
 
 
 foundone=false
 find $HOME -type f -atime +30 -size 100k |
 while true
 do
    read f
    echo "$f is over 100KB and has not been accessed in over 30 days"
    echo "Consider moving the file to archives."
    foundone=true
    # ------------------------------------
    echo "Subshell level = $BASH_SUBSHELL"
    # Subshell level = 1
    # Yes, we're inside a subshell.
    # ------------------------------------
 done
    
 #  foundone will always be false here since it is
 #+ set to true inside a subshell
 if [ $foundone = false ]
 then
    echo "No files need archiving."
 fi
 
 # =====================Now, here is the correct way:=================
 
 foundone=false
 for f in $(find $HOME -type f -atime +30 -size 100k)  # No pipe here.
 do
    echo "$f is over 100KB and has not been accessed in over 30 days"
    echo "Consider moving the file to archives."
    foundone=true
 done
    
 if [ $foundone = false ]
 then
    echo "No files need archiving."
 fi
 
 # ==================And here is another alternative==================
 
 #  Places the part of the script that reads the variables
 #+ within a code block, so they share the same subshell.
 #  Thank you, W.B.
 
 find $HOME -type f -atime +30 -size 100k | {
      foundone=false
      while read f
      do
        echo "$f is over 100KB and has not been accessed in over 30 days"
        echo "Consider moving the file to archives."
        foundone=true
      done
 
      if ! $foundone
      then
        echo "No files need archiving."
      fi
 }

A related problem occurs when trying to write the stdout of a tail -f piped to grep.
tail -f /var/log/messages | grep "$ERROR_MSG" >> error.log
 # The "error.log" file will not have anything written to it.

--

Using "suid" commands within scripts is risky, as it may compromise system security. [1]

Using shell scripts for CGI programming may be problematic. Shell script variables are not "typesafe", and this can cause undesirable behavior as far as CGI is concerned. Moreover, it is difficult to "cracker-proof" shell scripts.

Bash does not handle the double slash (//) string correctly.

Bash scripts written for Linux or BSD systems may need fixups to run on a commercial UNIX machine. Such scripts often employ GNU commands and filters which have greater functionality than their generic UNIX counterparts. This is particularly true of such text processing utilites as tr.

Danger is near thee --

Beware, beware, beware, beware.

Many brave hearts are asleep in the deep.

So beware --

Beware.

A.J. Lamb and H.W. Petrie

Notes

[1]

Setting the suid permission on the script itself has no effect.

Shell Wrappers

A "wrapper" is a shell script that embeds a system command or utility, that saves a set of parameters passed to that command. [1] Wrapping a script around a complex command line simplifies invoking it. This is expecially useful with sed and awk.

A sed or awk script would normally be invoked from the command line by a sed -e 'commands' or awk 'commands'. Embedding such a script in a Bash script permits calling it more simply, and makes it "reusable". This also enables combining the functionality of sed and awk, for example piping the output of a set of sed commands to awk. As a saved executable file, you can then repeatedly invoke it in its original form or modified, without the inconvenience of retyping it on the command line.

Example 33-1. shell wrapper

#!/bin/bash
 
 # This is a simple script that removes blank lines from a file.
 # No argument checking.
 #
 # You might wish to add something like:
 #
 # E_NOARGS=65
 # if [ -z "$1" ]
 # then
 #  echo "Usage: `basename $0` target-file"
 #  exit $E_NOARGS
 # fi
 
 
 # Same as
 #    sed -e '/^$/d' filename
 # invoked from the command line.
 
 sed -e /^$/d "$1"
 #  The '-e' means an "editing" command follows (optional here).
 #  '^' is the beginning of line, '$' is the end.
 #  This match lines with nothing between the beginning and the end,
 #+ blank lines.
 #  The 'd' is the delete command.
 
 #  Quoting the command-line arg permits
 #+ whitespace and special characters in the filename.
 
 #  Note that this script doesn't actually change the target file.
 #  If you need to do that, redirect its output.
 
 exit 0

Example 33-2. A slightly more complex shell wrapper

#!/bin/bash
 
 #  "subst", a script that substitutes one pattern for
 #+ another in a file,
 #+ i.e., "subst Smith Jones letter.txt".
 
 ARGS=3         # Script requires 3 arguments.
 E_BADARGS=65   # Wrong number of arguments passed to script.
 
 if [ $# -ne "$ARGS" ]
 # Test number of arguments to script (always a good idea).
 then
   echo "Usage: `basename $0` old-pattern new-pattern filename"
   exit $E_BADARGS
 fi
 
 old_pattern=$1
 new_pattern=$2
 
 if [ -f "$3" ]
 then
     file_name=$3
 else
     echo "File \"$3\" does not exist."
     exit $E_BADARGS
 fi
 
 
 #  Here is where the heavy work gets done.
 
 # -----------------------------------------------
 sed -e "s/$old_pattern/$new_pattern/g" $file_name
 # -----------------------------------------------
 
 #  's' is, of course, the substitute command in sed,
 #+ and /pattern/ invokes address matching.
 #  The "g", or global flag causes substitution for *every*
 #+ occurence of $old_pattern on each line, not just the first.
 #  Read the literature on 'sed' for an in-depth explanation.
 
 exit 0    # Successful invocation of the script returns 0.

Example 33-3. A generic shell wrapper that writes to a logfile

#!/bin/bash
 #  Generic shell wrapper that performs an operation
 #+ and logs it.
 
 # Must set the following two variables.
 OPERATION=
 #         Can be a complex chain of commands,
 #+        for example an awk script or a pipe . . .
 LOGFILE=
 #         Command-line arguments, if any, for the operation.
 
 
 OPTIONS="$@"
 
 
 # Log it.
 echo "`date` + `whoami` + $OPERATION "$@"" >> $LOGFILE
 # Now, do it.
 exec $OPERATION "$@"
 
 # It's necessary to do the logging before the operation.
 # Why?

Example 33-4. A shell wrapper around an awk script

#!/bin/bash
 # pr-ascii.sh: Prints a table of ASCII characters.
 
 START=33   # Range of printable ASCII characters (decimal).
 END=125
 
 echo " Decimal   Hex     Character"   # Header.
 echo " -------   ---     ---------"
 
 for ((i=START; i<=END; i++))
 do
   echo $i | awk '{printf("  %3d       %2x         %c\n", $1, $1, $1)}'
 # The Bash printf builtin will not work in this context:
 #     printf "%c" "$i"
 done
 
 exit 0
 
 
 #  Decimal   Hex     Character
 #  -------   ---     ---------
 #    33       21         !
 #    34       22         "
 #    35       23         #
 #    36       24         $
 #
 #    . . .
 #
 #   122       7a         z
 #   123       7b         {
 #   124       7c         |
 #   125       7d         }
 
 
 #  Redirect the output of this script to a file
 #+ or pipe it to "more":  sh pr-asc.sh | more

Example 33-5. A shell wrapper around another awk script

#!/bin/bash
 
 # Adds up a specified column (of numbers) in the target file.
 
 ARGS=2
 E_WRONGARGS=65
 
 if [ $# -ne "$ARGS" ] # Check for proper no. of command line args.
 then
    echo "Usage: `basename $0` filename column-number"
    exit $E_WRONGARGS
 fi
 
 filename=$1
 column_number=$2
 
 # Passing shell variables to the awk part of the script is a bit tricky.
 # See the awk documentation for more details.
 
 # A multi-line awk script is invoked by:  awk ' ..... '
 
 
 # Begin awk script.
 # -----------------------------
 awk '
 
 { total += $'"${column_number}"'
 }
 END {
      print total
 }     
 
 ' "$filename"
 # -----------------------------
 # End awk script.
 
 
 #   It may not be safe to pass shell variables to an embedded awk script,
 #+  so Stephane Chazelas proposes the following alternative:
 #   ---------------------------------------
 #   awk -v column_number="$column_number" '
 #   { total += $column_number
 #   }
 #   END {
 #       print total
 #   }' "$filename"
 #   ---------------------------------------
 
 
 exit 0

For those scripts needing a single do-it-all tool, a Swiss army knife, there is Perl. Perl combines the capabilities of sed and awk, and throws in a large subset of C, to boot. It is modular and contains support for everything ranging from object-oriented programming up to and including the kitchen sink. Short Perl scripts lend themselves to embedding in shell scripts, and there may even be some substance to the claim that Perl can totally replace shell scripting (though the author of this document remains skeptical).

Example 33-6. Perl embedded in a Bash script

#!/bin/bash
 
 # Shell commands may precede the Perl script.
 echo "This precedes the embedded Perl script within \"$0\"."
 echo "==============================================================="
 
 perl -e 'print "This is an embedded Perl script.\n";'
 # Like sed, Perl also uses the "-e" option.
 
 echo "==============================================================="
 echo "However, the script may also contain shell and system commands."
 
 exit 0

It is even possible to combine a Bash script and Perl script within the same file. Depending on how the script is invoked, either the Bash part or the Perl part will execute.

Example 33-7. Bash and Perl scripts combined

#!/bin/bash
 # bashandperl.sh
 
 echo "Greetings from the Bash part of the script."
 # More Bash commands may follow here.
 
 exit 0
 # End of Bash part of the script.
 
 # =======================================================
 
 #!/usr/bin/perl
 # This part of the script must be invoked with -x option.
 
 print "Greetings from the Perl part of the script.\n";
 # More Perl commands may follow here.
 
 # End of Perl part of the script.

bash$ bash bashandperl.sh
 Greetings from the Bash part of the script.
 
 
 bash$ perl -x bashandperl.sh
 Greetings from the Perl part of the script.
 	      
Оставьте свой комментарий !

Ваше имя:
Комментарий:
Оба поля являются обязательными

 Автор  Комментарий к данной статье