Advanced Bash-Scripting Guide: An in-depth exploration of the art of shell scripting | ||
---|---|---|
Prev | Chapter 12. External Filters, Programs and Commands | Next |
-exec COMMAND \;
在每一个find 匹配到的文件执行 COMMAND 命令. 命令序列以 ; 结束( ";" 是 转义符 以保证 shell 传递到find命令中的字符不会被解释为其他的特殊字符).
bash$ find ~/ -name '*.txt' /home/bozo/.kde/share/apps/karm/karmdata.txt /home/bozo/misc/irmeyc.txt /home/bozo/test-scripts/1.txt |
如果 COMMAND 中包含 {}, 那么 find 命令将会用所有匹配文件的路径名来替换 "{}" .
1 find ~/ -name 'core*' -exec rm {} \; 2 # 从用户的 home 目录中删除所有的 core dump文件. |
1 find /home/bozo/projects -mtime 1 2 # 列出最后一天被修改的 3 #+ 在/home/bozo/projects目录树下的所有文件. 4 # 5 # mtime = last modification time of the target file 6 # ctime = last status change time (via 'chmod' or otherwise) 7 # atime = last access time 8 9 DIR=/home/bozo/junk_files 10 find "$DIR" -type f -atime +5 -exec rm {} \; 11 # ^^ 12 # 大括号就是"find"命令用来替换目录的地方. 13 # 14 # 删除至少5天内没被存取过的 15 #+ "/home/bozo/junk_files" 中的所有文件. 16 # 17 # "-type filetype", where 18 # f = regular file 19 # d = directory, etc. 20 # ('find' 命令的 man页有完整的选项列表.) |
1 find /etc -exec grep '[0-9][0-9]*[.][0-9][0-9]*[.][0-9][0-9]*[.][0-9][0-9]*' {} \; 2 3 # 在/etc 目录中的文件找到所所有包含 IP 地址(xxx.xxx.xxx.xxx) 的文件. 4 # 可能会查找到一些多余的匹配. 我们如何去掉它们呢? 5 6 # 或许可以使用如下方法: 7 8 find /etc -type f -exec cat '{}' \; | tr -c '.[:digit:]' '\n' \ 9 | grep '^[^.][^.]*\.[^.][^.]*\.[^.][^.]*\.[^.][^.]*$' 10 # 11 # [:digit:] 是一种字符类.is one of the character classes 12 #+ 关于字符类的介绍见 POSIX 1003.2 标准化文档. 13 14 # Thanks, Stéphane Chazelas. |
find 命令的 -exec 选项不应该与shell中的内建命令 exec 相混淆. |
Example 12-3. 删除当前目录下文件名中包含一些特殊字符(包括空白)的文件..
1 #!/bin/bash 2 # badname.sh 3 # 删除当前目录下文件名中包含一些特殊字符的文件. 4 5 for filename in * 6 do 7 badname=`echo "$filename" | sed -n /[\+\{\;\"\\\=\?~\(\)\<\>\&\*\|\$]/p` 8 # badname=`echo "$filename" | sed -n '/[+{;"\=?~()<>&*|$]/p'` 这句也行. 9 # 删除文件名包含这些字符的文件: + { ; " \ = ? ~ ( ) < > & * | $ 10 # 11 rm $badname 2>/dev/null 12 # ^^^^^^^^^^^ 错误消息将被抛弃. 13 done 14 15 # 现在, 处理文件名中以任何方式包含空白的文件. 16 find . -name "* *" -exec rm -f {} \; 17 # "find"命令匹配到的目录名将替换到{}的位置. 18 # '\' 是为了保证 ';'被正确的转义, 并且放到命令的结尾. 19 20 exit 0 21 22 #--------------------------------------------------------------------- 23 # 这行下边的命令将不会运行, 因为 "exit" 命令. 24 25 # 这句是上边脚本的一个可选方法: 26 find . -name '*[+{;"\\=?~()<>&*|$ ]*' -exec rm -f '{}' \; 27 # (Thanks, S.C.) |
Example 12-4. 通过文件的 inode 号来删除文件
1 #!/bin/bash 2 # idelete.sh: 通过文件的inode号来删除文件. 3 4 # 当文件名以一个非法字符开头的时候, 这就非常有用了, 5 #+ 比如 ? 或 -. 6 7 ARGCOUNT=1 # 文件名参数必须被传递到脚本中. 8 E_WRONGARGS=70 9 E_FILE_NOT_EXIST=71 10 E_CHANGED_MIND=72 11 12 if [ $# -ne "$ARGCOUNT" ] 13 then 14 echo "Usage: `basename $0` filename" 15 exit $E_WRONGARGS 16 fi 17 18 if [ ! -e "$1" ] 19 then 20 echo "File \""$1"\" does not exist." 21 exit $E_FILE_NOT_EXIST 22 fi 23 24 inum=`ls -i | grep "$1" | awk '{print $1}'` 25 # inum = inode (索引节点) 号. 26 # -------------------------------------------------------- 27 # 每个文件都有一个inode号, 这个号用来记录文件物理地址信息. 28 # -------------------------------------------------------- 29 30 echo; echo -n "Are you absolutely sure you want to delete \"$1\" (y/n)? " 31 # 'rm' 命令的 '-v' 选项也会问这句话. 32 read answer 33 case "$answer" in 34 [nN]) echo "Changed your mind, huh?" 35 exit $E_CHANGED_MIND 36 ;; 37 *) echo "Deleting file \"$1\".";; 38 esac 39 40 find . -inum $inum -exec rm {} \; 41 # ^^ 42 # 大括号就是"find"命令 43 #+ 用来替换文本输出的地方. 44 echo "File "\"$1"\" deleted!" 45 46 exit 0 |
见 Example 12-27, Example 3-4, 和 Example 10-9 这些例子展示了使用 find 命令. 对于这个复杂而有强大的命令来说, 查看man页可以获得更多的细节.
这是给命令传递参数的一个过滤器, 也是组合多个命令的一个工具.它把一个数据流分割为一些足够小的块, 以方便过滤器和命令进行处理. 由此这个命令也是后置引用的一个强有力的替换. 在一般使用过多参数的命令替换失败的时候,用xargs 来替换它一般都能成功. [1] 通常情况下, xargs 从管道或者stdin中读取数据, 但是它也能够从文件的输出中读取数据.
xargs的默认命令是 echo. 这意味着通过管道传递给xargs的输入将会包含换行和空白, 不过通过xargs的处理, 换行和空白将被空格取代.
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 bash$ find ~/mail -type f | xargs grep "Linux" ./misc:User-Agent: slrn/0.9.8.1 (Linux) ./sent-mail-jul-2005: hosted by the Linux Documentation Project. ./sent-mail-jul-2005: (Linux Documentation Project Site, rtf version) ./sent-mail-jul-2005: Subject: Criticism of Bozo's Windows/Linux article ./sent-mail-jul-2005: while mentioning that the Linux ext2/ext3 filesystem . . . |
ls | xargs -p -l gzip 使用gzips 压缩当前目录下的每个文件, 一次压缩一个, 并且在每次压缩前都提示用户.
一个有趣的 xargs 选项是 -n NN, NN 是限制每次传递进来参数的个数. ls | xargs -n 8 echo 以每行8列的形式列出当前目录下的所有文件. |
另一个有用的选项是 -0, 使用 find -print0 或 grep -lZ 这两种组合方式. 这允许处理包含空白或引号的参数. find / -type f -print0 | xargs -0 grep -liwZ GUI | xargs -0 rm -f grep -rliwZ GUI / | xargs -0 rm -f 上边两行都可用来删除任何包含 "GUI" 的文件. (Thanks, S.C.) |
Example 12-5. Logfile: 使用 xargs 来监控系统 log
1 #!/bin/bash 2 3 # 从 /var/log/messagesGenerates 的尾部开始 4 # 产生当前目录下的一个lof 文件. 5 6 # 注意: 如果这个脚本被一个一般用户调用的话, 7 # /var/log/messages 必须是全部可读的. 8 # #root chmod 644 /var/log/messages 9 10 LINES=5 11 12 ( date; uname -a ) >>logfile 13 # 时间和机器名 14 echo --------------------------------------------------------------------- >>logfile 15 tail -$LINES /var/log/messages | xargs | fmt -s >>logfile 16 echo >>logfile 17 echo >>logfile 18 19 exit 0 20 21 # 注意: 22 # ----- 23 # 像 Frank Wang 所指出, 24 #+ 在原文件中的任何不匹配的引号(包括单引号和双引号) 25 #+ 都会给xargs造成麻烦. 26 # 27 # 他建议使用下边的这行来替换上边的第15行: 28 # tail -$LINES /var/log/messages | tr -d "\"'" | xargs | fmt -s >>logfile 29 30 31 32 # 练习: 33 # ----- 34 # 修改这个脚本, 使得这个脚本每个20分钟 35 #+ 就跟踪一下 /var/log/messages 的修改记录. 36 # 提示: 使用 "watch" 命令. |
在find命令中, 一对大括号就一个文本替换的位置.
Example 12-6. 把当前目录下的文件拷贝到另一个文件中
1 #!/bin/bash 2 # copydir.sh 3 4 # 拷贝 (verbose) 当前目录($PWD)下的所有文件到 5 #+ 命令行中指定的另一个目录下. 6 7 E_NOARGS=65 8 9 if [ -z "$1" ] # 如果没有参数传递进来那就退出. 10 then 11 echo "Usage: `basename $0` directory-to-copy-to" 12 exit $E_NOARGS 13 fi 14 15 ls . | xargs -i -t cp ./{} $1 16 # ^^ ^^ ^^ 17 # -t 是 "verbose" (输出命令行到stderr) 选项. 18 # -i 是"替换字符串"选项. 19 # {} 是输出文本的替换点. 20 # 这与在"find"命令中使用{}的情况很相像. 21 # 22 # 列出当前目录下的所有文件(ls .), 23 #+ 将 "ls" 的输出作为参数传递到 "xargs"(-i -t 选项) 中, 24 #+ 然后拷贝(cp)这些参数({})到一个新目录中($1). 25 # 26 # 最终的结果和下边的命令等价, 27 #+ cp * $1 28 #+ 除非有文件名中嵌入了"空白"字符. 29 30 exit 0 |
Example 12-7. 通过名字Kill进程
1 #!/bin/bash 2 # kill-byname.sh: 通过名字kill进程. 3 # 与脚本kill-process.sh相比较. 4 5 # 例如, 6 #+ 试一下 "./kill-byname.sh xterm" -- 7 #+ 并且查看你系统上的所有xterm都将消失. 8 9 # 警告: 10 # ----- 11 # 这是一个非常危险的脚本. 12 # 运行它的时候一定要小心. (尤其是以root身份运行时) 13 #+ 因为运行这个脚本可能会引起数据丢失或产生其他一些不好的效果. 14 15 E_BADARGS=66 16 17 if test -z "$1" # 没有参数传递进来? 18 then 19 echo "Usage: `basename $0` Process(es)_to_kill" 20 exit $E_BADARGS 21 fi 22 23 24 PROCESS_NAME="$1" 25 ps ax | grep "$PROCESS_NAME" | awk '{print $1}' | xargs -i kill {} 2&>/dev/null 26 # ^^ ^^ 27 28 # ----------------------------------------------------------- 29 # 注意: 30 # -i 参数是xargs命令的"替换字符串"选项. 31 # 大括号对的地方就是替换点. 32 # 2&>/dev/null 将会丢弃不需要的错误消息. 33 # ----------------------------------------------------------- 34 35 exit $? |
Example 12-8. 使用xargs分析单词出现的频率
1 #!/bin/bash 2 # wf2.sh: Crude word frequency analysis on a text file. 3 4 # 使用 'xargs' 将文本行分解为单词. 5 # 于后边的 "wf.sh" 脚本相比较. 6 7 8 # 检查命令行上输入的文件. 9 ARGS=1 10 E_BADARGS=65 11 E_NOFILE=66 12 13 if [ $# -ne "$ARGS" ] 14 # 纠正传递到脚本中的参数个数? 15 then 16 echo "Usage: `basename $0` filename" 17 exit $E_BADARGS 18 fi 19 20 if [ ! -f "$1" ] # 检查文件是否存在. 21 then 22 echo "File \"$1\" does not exist." 23 exit $E_NOFILE 24 fi 25 26 27 28 ################################################################# 29 cat "$1" | xargs -n1 | \ 30 # 列出文件, 每行一个单词. 31 tr A-Z a-z | \ 32 # 将字符转换为小写. 33 sed -e 's/\.//g' -e 's/\,//g' -e 's/ /\ 34 /g' | \ 35 # 过滤掉句号和逗号, 36 #+ 并且将单词间的空格修改为换行, 37 sort | uniq -c | sort -nr 38 # 最后统计出现次数,把数字显示在第一列,然后显示单词,并按数字排序. 39 ################################################################# 40 41 # 这个例子的作用与"wf.sh"的作用是一样的, 42 #+ 但是这个例子比较臃肿, 并且运行起来更慢一些(为什么?). 43 44 exit 0 |
通用求值表达式: 通过给定的操作(参数必须以空格分开)连接参数,并对参数求值.可以使算术操作, 比较操作, 字符串操作或者是逻辑操作.
返回 8
返回 2
返回错误消息, expr: division by zero
不允许非法的算术操作.
返回 15
在算术表达式expr中使用乘法操作时, 乘法符号必须被转义.
增加变量的值, 与 let y=y+1 和 y=$(($y+1)) 的效果相同. 这是使用算术表达式的一个例子.
在位置$position上提取$length长度的子串.
Example 12-9. 使用 expr
1 #!/bin/bash 2 3 # 展示一些 'expr'的使用 4 # ===================== 5 6 echo 7 8 # 算术 操作 9 # ---- ---- 10 11 echo "Arithmetic Operators" 12 echo 13 a=`expr 5 + 3` 14 echo "5 + 3 = $a" 15 16 a=`expr $a + 1` 17 echo 18 echo "a + 1 = $a" 19 echo "(incrementing a variable)" 20 21 a=`expr 5 % 3` 22 # 取模操作 23 echo 24 echo "5 mod 3 = $a" 25 26 echo 27 echo 28 29 # 逻辑 操作 30 # ---- ---- 31 32 # true返回 1 ,false 返回 0 , 33 #+ 而Bash的使用惯例则相反. 34 35 echo "Logical Operators" 36 echo 37 38 x=24 39 y=25 40 b=`expr $x = $y` # 测试相等. 41 echo "b = $b" # 0 ( $x -ne $y ) 42 echo 43 44 a=3 45 b=`expr $a \> 10` 46 echo 'b=`expr $a \> 10`, therefore...' 47 echo "If a > 10, b = 0 (false)" 48 echo "b = $b" # 0 ( 3 ! -gt 10 ) 49 echo 50 51 b=`expr $a \< 10` 52 echo "If a < 10, b = 1 (true)" 53 echo "b = $b" # 1 ( 3 -lt 10 ) 54 echo 55 # Note escaping of operators. 56 57 b=`expr $a \<= 3` 58 echo "If a <= 3, b = 1 (true)" 59 echo "b = $b" # 1 ( 3 -le 3 ) 60 # 也有 "\>=" 操作 (大于等于). 61 62 63 echo 64 echo 65 66 67 68 # 字符串 操作 69 # ------ ---- 70 71 echo "String Operators" 72 echo 73 74 a=1234zipper43231 75 echo "The string being operated upon is \"$a\"." 76 77 # 长度: 字符串长度 78 b=`expr length $a` 79 echo "Length of \"$a\" is $b." 80 81 # 索引: 从字符串的开头查找匹配的子串, 82 # 并取得第一个匹配子串的位置. 83 b=`expr index $a 23` 84 echo "Numerical position of first \"2\" in \"$a\" is \"$b\"." 85 86 # substr: 从指定位置提取指定长度的字串. 87 b=`expr substr $a 2 6` 88 echo "Substring of \"$a\", starting at position 2,\ 89 and 6 chars long is \"$b\"." 90 91 92 # 'match' 操作的默认行为就是 93 #+ 从字符串的开始进行搜索,并匹配第一个匹配的字符串. 94 # 95 # 使用正则表达式 96 b=`expr match "$a" '[0-9]*'` # 数字的个数. 97 echo Number of digits at the beginning of \"$a\" is $b. 98 b=`expr match "$a" '\([0-9]*\)'` # 注意需要转义括号 99 # == == + 这样才能触发子串的匹配. 100 echo "The digits at the beginning of \"$a\" are \"$b\"." 101 102 echo 103 104 exit 0 |
: 操作可以替换 match. 比如, b=`expr $a : [0-9]*`与上边所使用的 b=`expr match $a [0-9]*` 完全等价.
|
上边的脚本展示了expr是如何使用转义的括号对 -- \( ... \) -- 和 正则表达式 一起来分析和匹配子串. 下边是另外一个例子, 这次的例子是真正的应用用例.
1 # 去掉字符串开头和结尾的空白. 2 LRFDATE=`expr "$LRFDATE" : '[[:space:]]*\(.*\)[[:space:]]*$'` 3 4 # 来自于 Peter Knowle的 "booklistgen.sh" 脚本 5 #+ 用来将文件转换为Sony Librie格式. 6 # (http://booklistgensh.peterknowles.com) |
Perl, sed, 和 awk 是更强大的字符串分析工具. 在脚本中嵌入一段比较短的 sed 或 awk 操作 (见 Section 33.2) 比使用 expr 更加有吸引力.
见 Section 9.2 将会有更多使用 expr 进行字符串操作的例子.
[1] | 即使在不必非得强制使用 xargs 的时候, 使用 xargs 也可以明显地提高多文件批处理执行命令的速度. |