Commands for more advanced users
-exec COMMAND \;
Carries out COMMAND on each file that find matches. The command sequence terminates with \; (the ";" is escaped to make certain the shell passes it to find literally). If COMMAND contains {}, then find substitutes the full path name of the selected file for "{}".
bash$
find ~/ -name '*.txt'
/home/bozo/.kde/share/apps/karm/karmdata.txt
/home/bozo/misc/irmeyc.txt
/home/bozo/test-scripts/1.txt
|
find /home/bozo/projects -mtime 1
# Lists all files in /home/bozo/projects directory tree
#+ that were modified within the last day.
#
# mtime = last modification time of the target file
# ctime = last status change time (via 'chmod' or otherwise)
# atime = last access time
DIR=/home/bozo/junk_files
find "$DIR" -type f -atime +5 -exec rm {} \;
# Deletes all files in "/home/bozo/junk_files"
#+ that have not been accessed in at least 5 days.
#
# "-type filetype", where
# f = regular file
# d = directory, etc.
# (The 'find' manpage has a complete listing.)
|
find /etc -exec grep '[0-9][0-9]*[.][0-9][0-9]*[.][0-9][0-9]*[.][0-9][0-9]*' {} \;
# Finds all IP addresses (xxx.xxx.xxx.xxx) in /etc directory files.
# There a few extraneous hits - how can they be filtered out?
# Perhaps by:
find /etc -type f -exec cat '{}' \; | tr -c '.[:digit:]' '\n' \
| grep '^[^.][^.]*\.[^.][^.]*\.[^.][^.]*\.[^.][^.]*$'
# [:digit:] is one of the character classes
# introduced with the POSIX 1003.2 standard.
# Thanks, S.C.
|
![]() |
The -exec option to find should not be confused with the exec shell builtin. |
Example 12-2. Badname, eliminate file names in current directory containing bad characters and whitespace.
#!/bin/bash
# Delete filenames in current directory containing bad characters.
for filename in *
do
badname=`echo "$filename" | sed -n /[\+\{\;\"\\\=\?~\(\)\<\>\&\*\|\$]/p`
# Files containing those nasties: + { ; " \ = ? ~ ( ) < > & * | $
rm $badname 2>/dev/null # So error messages deep-sixed.
done
# Now, take care of files containing all manner of whitespace.
find . -name "* *" -exec rm -f {} \;
# The path name of the file that "find" finds replaces the "{}".
# The '\' ensures that the ';' is interpreted literally, as end of command.
exit 0
#---------------------------------------------------------------------
# Commands below this line will not execute because of "exit" command.
# An alternative to the above script:
find . -name '*[+{;"\\=?~()<>&*|$ ]*' -exec rm -f '{}' \;
exit 0
# (Thanks, S.C.)
|
Example 12-3. Deleting a file by its inode number
#!/bin/bash
# idelete.sh: Deleting a file by its inode number.
# This is useful when a filename starts with an illegal character,
#+ such as ? or -.
ARGCOUNT=1 # Filename arg must be passed to script.
E_WRONGARGS=70
E_FILE_NOT_EXIST=71
E_CHANGED_MIND=72
if [ $# -ne "$ARGCOUNT" ]
then
echo "Usage: `basename $0` filename"
exit $E_WRONGARGS
fi
if [ ! -e "$1" ]
then
echo "File \""$1"\" does not exist."
exit $E_FILE_NOT_EXIST
fi
inum=`ls -i | grep "$1" | awk '{print $1}'`
# inum = inode (index node) number of file
# Every file has an inode, a record that hold its physical address info.
echo; echo -n "Are you absolutely sure you want to delete \"$1\" (y/n)? "
# The '-v' option to 'rm' also asks this.
read answer
case "$answer" in
[nN]) echo "Changed your mind, huh?"
exit $E_CHANGED_MIND
;;
*) echo "Deleting file \"$1\".";;
esac
find . -inum $inum -exec rm {} \;
echo "File "\"$1"\" deleted!"
exit 0
|
See Example 12-22, Example 3-4, and Example 10-9 for scripts using find. Its manpage provides more detail on this complex and powerful command.
A filter for feeding arguments to a command, and also a tool for assembling the commands themselves. It breaks a data stream into small enough chunks for filters and commands to process. Consider it as a powerful replacement for backquotes. In situations where backquotes fail with a too many arguments error, substituting xargs often works. Normally, xargs reads from stdin or from a pipe, but it can also be given the output of a file.
The default command for xargs is echo. This means that input piped to xargs may have linefeeds and other whitespace characters stripped out.
bash$
ls -l
total 0
-rw-rw-r-- 1 bozo bozo 0 Jan 29 23:58 file1
-rw-rw-r-- 1 bozo bozo 0 Jan 29 23:58 file2
bash$
ls -l | xargs
total 0 -rw-rw-r-- 1 bozo bozo 0 Jan 29 23:58 file1 -rw-rw-r-- 1 bozo bozo 0 Jan 29 23:58 file2
|
ls | xargs -p -l gzip gzips every file in current directory, one at a time, prompting before each operation.
![]() |
An interesting xargs option is -n NN , which limits to NN the number of arguments passed. ls | xargs -n 8 echo lists the files in the current directory in 8 columns. |
![]() |
Another useful option is -0, in combination with find -print0 or grep -lZ. This allows handling arguments containing whitespace or quotes. find / -type f -print0 | xargs -0 grep -liwZ GUI | xargs -0 rm -f grep -rliwZ GUI / | xargs -0 rm -f Either of the above will remove any file containing "GUI". (Thanks, S.C.) |
Example 12-4. Logfile using xargs to monitor system log
#!/bin/bash
# Generates a log file in current directory
# from the tail end of /var/log/messages.
# Note: /var/log/messages must be world readable
# if this script invoked by an ordinary user.
# #root chmod 644 /var/log/messages
LINES=5
( date; uname -a ) >>logfile
# Time and machine name
echo --------------------------------------------------------------------- >>logfile
tail -$LINES /var/log/messages | xargs | fmt -s >>logfile
echo >>logfile
echo >>logfile
exit 0
# Exercise:
# --------
# Modify this script to track changes in /var/log/messages at intervals
#+ of 20 minutes.
# Hint: Use the "watch" command.
|
Example 12-5. copydir, copying files in current directory to another, using xargs
#!/bin/bash
# Copy (verbose) all files in current directory
# to directory specified on command line.
if [ -z "$1" ] # Exit if no argument given.
then
echo "Usage: `basename $0` directory-to-copy-to"
exit 65
fi
ls . | xargs -i -t cp ./{} $1
# This is the exact equivalent of
# cp * $1
# unless any of the filenames has "whitespace" characters.
exit 0
|
All-purpose expression evaluator: Concatenates and evaluates the arguments according to the operation given (arguments must be separated by spaces). Operations may be arithmetic, comparison, string, or logical.
returns 8
returns 2
returns 15
The multiplication operator must be escaped when used in an arithmetic expression with expr.
Increment a variable, with the same effect as let y=y+1 and y=$(($y+1)) . This is an example of arithmetic expansion.
Extract substring of $length characters, starting at $position.
Example 12-6. Using expr
#!/bin/bash
# Demonstrating some of the uses of 'expr'
# =======================================
echo
# Arithmetic Operators
# ---------- ---------
echo "Arithmetic Operators"
echo
a=`expr 5 + 3`
echo "5 + 3 = $a"
a=`expr $a + 1`
echo
echo "a + 1 = $a"
echo "(incrementing a variable)"
a=`expr 5 % 3`
# modulo
echo
echo "5 mod 3 = $a"
echo
echo
# Logical Operators
# ------- ---------
# Returns 1 if true, 0 if false,
#+ opposite of normal Bash convention.
echo "Logical Operators"
echo
x=24
y=25
b=`expr $x = $y` # Test equality.
echo "b = $b" # 0 ( $x -ne $y )
echo
a=3
b=`expr $a \> 10`
echo 'b=`expr $a \> 10`, therefore...'
echo "If a > 10, b = 0 (false)"
echo "b = $b" # 0 ( 3 ! -gt 10 )
echo
b=`expr $a \< 10`
echo "If a < 10, b = 1 (true)"
echo "b = $b" # 1 ( 3 -lt 10 )
echo
# Note escaping of operators.
b=`expr $a \<= 3`
echo "If a <= 3, b = 1 (true)"
echo "b = $b" # 1 ( 3 -le 3 )
# There is also a "\>=" operator (greater than or equal to).
echo
echo
# Comparison Operators
# ---------- ---------
echo "Comparison Operators"
echo
a=zipper
echo "a is $a"
if [ `expr $a = snap` ]
# Force re-evaluation of variable 'a'
then
echo "a is not zipper"
fi
echo
echo
# String Operators
# ------ ---------
echo "String Operators"
echo
a=1234zipper43231
echo "The string being operated upon is \"$a\"."
# length: length of string
b=`expr length $a`
echo "Length of \"$a\" is $b."
# index: position of first character in substring
# that matches a character in string
b=`expr index $a 23`
echo "Numerical position of first \"2\" in \"$a\" is \"$b\"."
# substr: extract substring, starting position & length specified
b=`expr substr $a 2 6`
echo "Substring of \"$a\", starting at position 2,\
and 6 chars long is \"$b\"."
# The default behavior of the 'match' operations is to
#+ search for the specified match at the ***beginning*** of the string.
#
# uses Regular Expressions
b=`expr match "$a" '[0-9]*'` # Numerical count.
echo Number of digits at the beginning of \"$a\" is $b.
b=`expr match "$a" '\([0-9]*\)'` # Note that escaped parentheses
# == == + trigger substring match.
echo "The digits at the beginning of \"$a\" are \"$b\"."
echo
exit 0
|
![]() |
The : operator can substitute for match. For example, b=`expr $a : [0-9]*` is the exact equivalent of b=`expr match $a [0-9]*` in the above listing.
|
This example illustrates how expr uses the escaped parentheses -- \( ... \) -- grouping operator in tandem with regular expression parsing to match a substring.
Perl, sed, and awk have far superior string parsing facilities. A short sed or awk "subroutine" within a script (see Section 34.2) is an attractive alternative to using expr.
See Section 9.2 for more on string operations.