管道命令
-
管道命令只能处理 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 命令用于从标准输入生成并执行命令。它会把标准输入转换为命令的参数。
grep、awk 这样的命令,既允许把输入做为命令行参数,也可以把标准输入做为输入(管道)。如:
grep 'hello' file 把输入做为参数
cat file | grep 'hello' 把标准输入做为输入
但 cp、echo 这类的命令只能把输入做为参数,不支持管道,于是 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 经常搭配 ls、find、grep 一起使用。
实际应用中,有时文件列表是由脚本或命令产生的,有可能会在文件名中含有空格。
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 来保证输入列表不会被误切。