shell技巧-xargs代替for循环格式化多列数据
之前对xargs命令理解不够透彻,在shell脚本中对于多列文本格式化参数一直用for来实现,最近深入研究了一下,xargs可以完全代替for循环使用!
假设这里有一个包含两列内容的txt文本(ip和port)。
[root@VM-0-15-centos test]# cat ip-port.txt 222.69.242.108 80 47.100.103.111 80 119.3.228.163 8888 139.159.156.229 9080 116.63.90.90 9443
我想用curl去批量请求这些ip获取状态码,按照我以往的写法如下:
[root@VM-0-15-centos test]# for i in $(cat ip-port.txt | awk '{printf "%s:%s\n",$1,$2}');do curl -m1 -s -o /dev/null -w "%{http_code} %{url_effective}\n" $i ;done 302 HTTP://222.69.242.108:80/ 000 HTTP://47.100.103.111:80/ 302 HTTP://119.3.228.163:8888/ 302 HTTP://139.159.156.229:9080/ 000 HTTP://116.63.90.90:9443/
这样可以实现,但是写法过于复杂。在for循环之前还需要用awk格式化输出。
之前误认为xargs无法处理多列的数据,但实际是可以通过一些trick来实现。
换成xargs的写法如下:
[root@VM-0-15-centos test]# cat ip-port.txt | xargs -n2 sh -c 'curl -m1 -s -o /dev/null -w "%{http_code} %{url_effective}\n" $1:$2' _ 302 HTTP://222.69.242.108:80/ 000 HTTP://47.100.103.111:80/ 302 HTTP://119.3.228.163:8888/ 000 HTTP://139.159.156.229:9080/ 000 HTTP://116.63.90.90:9443/
使用xargs调用sh就巧妙地省略了字符串单独格式化处理的步骤!
注意:
- xargs命令最后还有一个下划线一定不能省略!
- sh -c 后面的命令要用单引号,否则$1,$2会被当前shell当作变量解析!
由于使用了sh -c ,相当于调用了一个子shell。而shell中的参数传递如下:
- $0 Shell本身的文件名
- $1~$n 添加到Shell的各参数值。$1是第1参数、$2是第2参数…。
如果不加下划线会怎么样?
[root@VM-0-15-centos test]# cat ip-port.txt | xargs -n2 sh -c 'curl -m1 -s -o /dev/null -w "%{http_code} %{url_effective}\n" $1:$2' 000 HTTP://80:/ 000 HTTP://80:/ 000 HTTP://8888:/ 000 HTTP://9080:/ 000 HTTP://9443:/
$1实际取到的是第二列的参数,而$2为空值。此时$0才是第一列的内容!
为什么会这样呢?查阅了sh的文档,bash也一样,解释如下:
-c string 如果有-c选项,那么命令将从字符串中读取。 如果字符串后面有参数,它们将被分配给位置参数。参数从$0开始。
man sh
使用-c参数相当于默认没有shell文件名,默认传参就是从$0开始。但是这不符合我们的使用习惯,所以在命令最后加上一个下划线代替文件名作为$0。这样参数就从$1开始了。
至于n2的作用还是解释一下,分为两列。
[root@VM-0-15-centos test]# head -5 ip-port.txt | xargs 222.69.242.108 80 47.100.103.111 80 119.3.228.163 8888 139.159.156.229 9080 116.63.90.90 9443 [root@VM-0-15-centos test]# head -5 ip-port.txt | xargs -n2 222.69.242.108 80 47.100.103.111 80 119.3.228.163 8888 139.159.156.229 9080 116.63.90.90 9443
补充:上面curl的状态码有些是000,因为我超时时间设置为1s,部分请求在1s内请求未完成导致的。
2022年10月20日 更新
xargs注意一定要带上 -n 参数,比如格式化的数据是3列就一定要 带上-n3,如果是5列就带上-n5。原因是传给xargs的数据全部都在一行,用-n重新排列。
另外xargs也可以不用sh -c 来格式化数据,用printf。
root@VM-8-15-ubuntu:~/test/output# seq 1 10 | xargs -n2 1 2 3 4 5 6 7 8 9 10 root@VM-8-15-ubuntu:~/test/output# seq 1 10 | xargs -n2 printf "first %s second %s\n" first 1 second 2 first 3 second 4 first 5 second 6 first 7 second 8 first 9 second 10 root@VM-8-15-ubuntu:~/test/output#
但是不能调整顺序比如1和2的位置,会麻烦一些。
2023年08月08日 更高级的用法
对xargs的理解更加深刻,假如说我们把xargs sh -c 放在了脚本里面执行。但是shell脚本也会接受传参,那么如何把shell的参数同样也放到xargs中执行呢?
把脚本的参数追加到xargs sh的后面。
[root@VM-4-7-centos ~]# cat test.sh seq 1 10 | xargs -n1 sh -c 'echo 脚本的参数:$1, 脚本的参数:$2, xargs的参数:$3' _ $1 $2 [root@VM-4-7-centos ~]# [root@VM-4-7-centos ~]# bash -x test.sh 参数1 参数2 + seq 1 10 + xargs -n1 sh -c 'echo 脚本的参数:$1, 脚本的参数:$2, xargs的参数:$3' _ $'\345\217\202\346\225\2601' $'\345\217\202\346\225\2602' 脚本的参数:参数1, 脚本的参数:参数2, xargs的参数:1 脚本的参数:参数1, 脚本的参数:参数2, xargs的参数:2 脚本的参数:参数1, 脚本的参数:参数2, xargs的参数:3 脚本的参数:参数1, 脚本的参数:参数2, xargs的参数:4 脚本的参数:参数1, 脚本的参数:参数2, xargs的参数:5 脚本的参数:参数1, 脚本的参数:参数2, xargs的参数:6 脚本的参数:参数1, 脚本的参数:参数2, xargs的参数:7 脚本的参数:参数1, 脚本的参数:参数2, xargs的参数:8 脚本的参数:参数1, 脚本的参数:参数2, xargs的参数:9 脚本的参数:参数1, 脚本的参数:参数2, xargs的参数:10
2024年05月30日 sh -c和 -I 可以搭配使用
之前一直以为sh -c 只能通过传参的方式执行,但实际可以和-I配合。
[root@VM-4-7-centos ~]# echo 1 2 3 | xargs -d ' ' -n1 -I {} sh -c 'echo "{}"' 1 2 3
这里需要加上-d ‘ ‘ 来作为分隔符,否则得到的结果如下。
[root@VM-4-7-centos ~]# echo 1 2 3 | xargs -n1 -I {} sh -c 'echo "{}" ' 1 2 3
默认情况下,xargs
将输入中的所有内容视为单个参数,并传递给命令。
微信赞赏支付宝赞赏
发表评论