---
title: xargs命令
date: 2019-8-23 23:05:33
tags: 
  - linux
categories: 
  - linux
---

`xargs`命令的作用可以概括为将标准输入转化为命令行参数

<!-- more -->

### 标准输入与管道符

Linux下有些命令是可以接受标准输入的
例如下面这些命令
+ `cat` 查看文件内容
+ `sort` 对文件内容的行进行排序
+ `uniq` 报告或忽略文件中重复的行，必须先进行排序，所以常与sort配合使用
+ `grep` 根据规则找出匹配到的行
+ `wc` 获得文件中换行符，字，和字节个数
+ `head` 输出文件的开头部分
+ `tail` 输出文件的结尾部分
+ `tee` 从标准输入读取数据，并同时写到标准输出和文件

#### 标准输入的重定向
我们以sort命令为例
所有可以接受标准输入的默认标准输入指向都是**控制台输入**
如果直接执行sort
![默认标准输入](/images/linux/io/默认标准输入.png)
如果我们把这些内容写到**1.txt**这个文件当中
就可以执行

```bash
sort 1.txt
# 或者
sort < 1.txt
```
需要注意的是这两种写法虽然效果一样，但是含义是不同的
前者是给sort命令的传参，后者则是输入重定向

查看sort命令的帮助可以看到相关说明
![sort help](/images/linux/io/sort_help.png)
> 如果没有指定文件，则从标准输入读取

所以前者并没有改变标准输入的指向，只是因为指定了文件，该命令本身不再从标准输入读取
后者并没有指定文件，而是改变了标准输入的指向

#### 管道符
Linux当中的`|`称为管道符，使用方式为
```
command1 | command2
```
它的作用是把command1的标准输出作为command2的标准输入，相当于对command2做了标准输入的重定向
可以利用它完成较为复杂的链式处理
比如
```bash
tail < 1.log | sort | uniq -d -c | grep 'ab'
```
表示的含义是
1. 读取1.log文件的最后10行
2. 将这10行的内容作为sort的标准输入
3. 将排序后的内容作为uniq的标准输入，找到其中重复出现的行，以及出现的次数
4. 重复出现的行内容作为grep的标准输入，从中筛选包含字符串ab行


### xargs命令
使用管道符进行链式处理非常方便，但是能够接受标准输入的命令其实很少
大多数命令都不接受，只能接收命令行的传参
比如常见的`ls`命令，用来查看目录下的文件，它就不接受标准输入
例如现在有**apache2**和**php-apache**这两个目录，我们要列出这两个目录里面各自包含的文件
先创建好一个input.txt文件，内容就是
```
apache2 php-apache
```
命令写法
```bash
# 当然可以这样做，常规的传参方式
ls apache2 php-apache

# 如果要从其他来源读取要列出内容的目录呢
ls < input.txt #这样是无效的
cat input.txt | ls #这样与上面的是在做一样的事，同样无效

# 必须使用xargs把标准输出转化为传参
cat input.txt | xargs ls
```
管道符把标准输出作为xargs的标准输入
xargs则把标准输入转化为了命令行参数，传递给后面真正要执行的命令，在这里就是ls

> 如果xargs后面没有跟任何命令，那么就代表跟的是echo
echo命令的作用就是把接收到的参数输出到标准输出

#### 单独使用
xargs虽然有这些作用，但是它本身仍然是个可以接受标准输入的命令
如果没有进行标准输入的重定向 (`<`或者`|`)
那么它的标准输入就是指向控制台，后面相当于跟着echo
单独执行xargs的表现也就可以理解了
```bash
root@ubuntu:~$ xargs
abcd #Ctrl+D
abcd
```
命令行会等待用户输入，并且把这些输入直接输出

如果跟上了其他命令呢
```bash
root@ubuntu:~$ xargs find -name
*.txt #Ctrl+D
./input.txt
./1.txt
```
执行后会等待用户输入，Ctrl+D结束输入
把这些输入的内容作为参数提供给find命令
所以相当于执行`find -name *.txt`

### 其他使用技巧
#### 与find的搭配使用

如果我们要删除7天之前修改的文件
由于rm命令只能按照文件名匹配，find则具备多种方式的文件查找
所以两者搭配使用更加方便


先熟悉一下find命令如何按时间查找文件

|+n|n|-n|
|--|-|--|
|(n+1)\*24H前|(n+1)\*24H ~ n\*24H之间|n*24H以内|

+ `[a|c|m]min`    \[最后访问|最后状态修改|最后内容修改\]min(单位是分钟)
+ `[a|c|m]time`    \[最后访问|最后状态修改|最后内容修改\]time(单位是天)

```bash
# 查找当前目录7天之前修改的文件，并且作为rm的参数执行删除
find ./ -mtime +6 | xargs rm
```
上面的写法有个问题，就是xargs是使用**空白**作为参数的分隔的，但是文件名可能包含空格
应该使用下面的写法
```bash
find ./ -mtime +6 -print0 | xargs -0 rm
```
`-print0`提供给find命令，代表输出的文件列表以null作为分隔
`-0`提供给xargs命令，代表以null作为分隔来切分参数列表

#### -p 和 -t 参数
过长的命令串联有种难以掌控的感觉，如果出现了失误可能造成无法挽回的结果
`-t`参数可以在执行xargs后面命令之前打印出要执行的内容
`-p`参数会等待用户确认，在用户输入y（大小写皆可）后再执行


#### -L 参数
xargs把标准输入转化为参数提供给后面的命令执行，默认情况下是执行一次的
如果我们要把每一行的输出循环执行呢
这就需要用到`-L`参数了，后面跟上行数，代表把几行作为一组参数传递给后面的命令执行一次
![xargs循环执行](/images/linux/io/xargs循环执行.png)

<del>要问带\n的字符串为啥这样写，我只能说shell的晦涩之处不止于此</del>
可以看到前一次执行了`ls -l -h`
后一次分别执行了`ls -l`和`ls -h`