管道命令

  • 管道命令只能处理 STDOUT 而不能处理 STDERR

  • 每个管道命令必须能够接受 STDIN

参数转换: XARGS

xargs 可以给不支持管道的命令提供参数,参数来自其读取的 STDIN,一般以空格或换行符为分隔符。

xargs [-0epn] command

xargs 把 STDIN 转换为参数,放到 command 后面。

-0 还原特殊字符

-e EOF

-p 每条命令都请示用户

-n 一次用几个参数

如果 xargs 后面没有任何命令,默认是用 echo 来输出。

范例

  • 提取 /etc/passwd 的用户名(第一栏),取三行,用 id 命令显示每个帐号信息

cut -d ':' -f 1 /etc/passwd | head -n 3 | xargs -n 1 id

-n 表示一次用一个字符串做参数

  • 每次执行 id 时,都请用户确认

cut -d ':' -f 1 /etc/passwd | head -n 3 | xargs -p -n 1 id

  • 所有的 /etc/passwd 内的帐号都用 id 查看,查到 sync 结束

cut -d ':' -f 1 /etc/passwd | xargs -e'sync' -n 1 id

🚩 -e’sync’ 要连在一起,中间不能有空格。

  • 找出 /usr/sbin 下面具有特殊权限的文件名,并使用 ls -l 列出详细属性
find /usr/sbin -perm /7000 | xargs ls -l
# 以上命令等效于下行命令:
ls -l $(find /usr/sbin -perm /7000)

关于减号 - 的用途

在管道命令中,某些命令使用时需要 文件名,如果希望用 STDOUT 或 STDIN 来替换,可以用 “-” 来代替文件名。

mkdir /tmp/homeback
tar -cvf - /home | tar -xvf - -C /tmp/homeback
#        ^ 代替目标压缩包    ^ 代替源压缩包

创建管道

使用 管道操作符 |,可以把前一个命令的标准输出重定向到后一个命令的标准输入。

管道可以把标准输出重定向到标准输入,无法重定向标准错误

可以使用多个操作符,实现多次重定向。

其中许多命令可以用 - 符号做为参数,来原位 代替文件名,表示此处的输入不是来自文件,而是来自 标准输入

command1 | command2 paramater1 | command3 parameter1 - parameter2 | command4

把输出做为参数

命令替换

命令替换可以捕获任何命令的 输出,作为另一个命令的 参数

如果把一个命令行放在 反引号 )中,Shell 会 先运行该命令,然后用输出替换整个表达式(包括反引号)。经常用于给变量赋值。

可以对任何写到标准输出的命令执行命令替换。

today=`date` 将代表当前日期的字符串指定给 today 变量。

files=`ls | wc -l` 将当前目录中的文件数保存在 files 变量中。

  • 嵌套命令替换时,嵌套的反引号需 转义

logmsg=`echo Your login directory is \`pwd\` `

XARGS

xargs 命令用于从标准输入生成并执行命令。它会把标准输入转换为命令的参数。

grepawk 这样的命令,既允许把输入做为命令行参数,也可以把标准输入做为输入(管道)。如:

grep 'hello' file 把输入做为参数

cat file | grep 'hello' 把标准输入做为输入

cpecho 这类的命令只能把输入做为参数,不支持管道,于是 xargs 有了用武之地。

  • xargs 默认使用空格、tab、换行符做为分隔符

  • 如果没有指定命令,默认使用 echo 执行。

  • 默认情况下,xargs 会把输入一次性传递给命令,所有输入只做为 一个参数 来执行。

  • 若使用分隔符,输入被拆分以后,每一部分单独做为参数,分别执行 一次命令。

使用方法

  • 默认没有分隔符
~]$ cat text1
1 apple
2 pear
3 banana

~]$ xargs < text1
1 apple 2 pear 3 banana

之所以三行变成了一行,是因为最终执行的命令是 echo '1 apple 2 pear 3 banana',所有的文件内容被一次性全部做为参数交给命令。

  • 指定分隔符

-d 参数来指定分隔符。

~]$ echo 'for i in "$@"; do echo arg: $i; done; echo DONE' > /tmp/test.sh

~]$ printf %b 'ac s\nbc s\ncc s\n' | xargs -d '\n' bash /tmp/test.sh
#           指定分隔符为换行符,要用引号
arg: ac s
arg: bc s
arg: cc s
DONE

%b 使得 printf 识别以反斜线开头的特殊字符。-d 用于指定分隔符。

  • 指定每次用几个字段来做参数

使用 -n--max-args 参数,加上整数。

~]$ xargs<text1 echo "arg list:"
arg list: 1 apple 2 pear 3 banana

~]$ xargs --max-args 3 <text1 echo "arg list:"
arg list: 1 apple 2
arg list: pear 3 banana

~]$ xargs -n 1 <text1 echo "arg list:"
arg list: 1
arg list: apple
arg list: 2
arg list: pear
arg list: 3
arg list: banana
  • 忽略输入中被引用的空白

如果输入中的空白被单引号或双引号保护,或被转义,xargs 不会在此分割。

~]$ echo '"4 plum"' | cat text1 -
1 apple
2 pear
3 banana
"4 plum"

~]$ echo '"4 plum"' | cat text1 - | xargs -n 1
1
apple
2
pear
3
banana
4 plum
  • 指定替换字符串

如果参数的位置不是在命令的最后,可以先用 -I 指定替换字符串,然后在参数的正确位置用字符串代替,xargs 在执行命令时会把切割出来的参数放在这个位置来执行。

选项 -L 允许把输入的 每一行 切割成一段,做为参数交给命令。

~]$ xargs -I XYZ echo "START XYZ REPEAT XYZ END" <text1
#            ^^^  XYZ 为替换字符串
START 1 apple REPEAT 1 apple END
START 2 pear REPEAT 2 pear END
START 3 banana REPEAT 3 banana END

~]$ xargs -I X echo "<X><X>" <text2    
#            ^ X 为替换字符串
<9   plum><9   plum>
<3   banana><3 banana>
<10  apple><10 apple>

~]$ cat text1 text2 | xargs -L 2     
# 把 2 行输入切成一段做参数  ^^ ^
1 apple 2 pear
3 banana 9 plum
3 banana 10 apple
  • 处理文件名中的空白

xargs 经常搭配 lsfindgrep 一起使用。

实际应用中,有时文件列表是由脚本或命令产生的,有可能会在文件名中含有空格。

ls 可以使用 --quoting-style 选项把含有空格的文件名在结果中强制引用或转义。

~]$ cp text1 "text 1"
#                 ^ 生成文件名含空格的文件

~]$ ls *1 | xargs grep "1"
text1:1 apple
grep: text: No such file or directory
grep: 1: No such file or directory
# 直接运行 ls 会报错,因为文件名被分割后解析

~]$ ls --quoting-style escape *1
text1  text\ 1
# 以转义的形式显示文件名

~]$ ls --quoting-style shell *1
text1  'text 1'
# 以强引用的形式显示文件名

~]$ ls --quoting-style shell *1 |xargs grep "1"
text1:1 apple
text 1:1 apple
# 不再报错

更好的解决办法是使用 xargs-0 选项,用空字符 \0 来分割参数。

~]$ ls *1 | tr '\n' '\0' | xargs -0 grep "1"
text1:1 apple
text 1:1 apple

先把文件列表中的回车替换为空字符,再使用 xargs-0 选项,即可解决文件名中空格的问题。

虽然 ls 命令不支持输出以空字符为分隔符的文件列表,但很多其它命令都支持。

find -exec

find 命令的 -exec 选项,其作用非常像 find -print0 | xargs -0

不同之处在于:

  • 必须使用 {} 来标出文件名在命令中的位置

  • 必须用加号 + 结束命令

  • 命令对每个输入文件均执行一遍

早期版本中,find -exec必须用分号结束命令,而且分号必须被转义:\;';'";" 均可。但这种格式返回的结果中会没有文件名,非常不人性化。find + 完美解决了该问题。

~]$ find . -name "*1" -exec grep "1" {} \;
#    用分号结束命令,结果会不显示文件名  ^^
1 apple
1 apple

~]$ find . -name "*1" -print0 | xargs -0 grep "1"
./text1:1 apple
./text 1:1 apple
# find -print0 配合 xargs -0 没有问题


~]$ find . -name "*1" -exec grep "1" {} +
./text1:1 apple
./text 1:1 apple
# find 用加号就可以解决了,不再需要 xargs

如果现实情况要求必须要搭配使用 find | xargs,只要不能确认输入完全没问题,就应该尽可能地使用 find -print0 | xargs -0 来保证输入列表不会被误切。