shell技巧-xargs代替for循环格式化多列数据

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 将输入中的所有内容视为单个参数,并传递给命令。

赞赏

微信赞赏支付宝赞赏

Zgao

愿有一日,安全圈的师傅们都能用上Zgao写的工具。

发表评论