Linux常用命令--管道符"|"、"xargs"、文件描述符与重定向

摘要

  • 本文介绍管道符"|"与"xargs"的使用方法。

  • 以及文件描述符与重定向操作符的使用方法。

  • 本文基于CentOS8(x86_64)

管道符"|"

  • 管道符主要用于多重命令处理,前面命令的输出结果作为后面命令的输入

1
2
# 该命令的作用就是查看文本后排序,然后再去重,最后过滤出含有hello的行并输出
cat test.txt | sort | uniq | grep "hello"
  • 以上的cat、sort、uniq、grep等命令均支持管道符,一般情况下,处理文本的命令,例如sort、uniq、grep、awk、sed等命令均支持管道,是因为这些命令均可从标准输入中读取要处理的文本(即从标准输入中读取参数)

  • 而对于不是处理文本的命令,例如ls、rm、kill等则不支持从标准输入中读取参数,只支持从命令行中读取参数,而要使其也能从标准输入中读取参数则需要使用xargs

  • 命令行参数优先于标准输入即管道符,如cat a.txt | sort b.txt,此时sort仅仅会处理b.txt

  • - 表示标准输入,例如 cat a.txt | sort b.txt -,相当于 sort a.txt b.txt

命令行参数和标准输入的区别

  • 命令行参数

    命令行参数就是命令后面的参数
    例如rm test.shrm是命令,test.sh就是命令行参数

  • 标准输入

    标准输入一般指的是键盘输入,也可用于表示管道符之前命令的输出结果(即打印在屏幕的文本)作为之后命令的标准输入

xargs

  • xargs的作用是接收管道符前面命令的输出进行处理后作为命令行参数传递给后续命令,而并不是作为标准输入传递给后续命令。

  • xargs以空白字符(空格,tap,换行符均算空白字符)分隔从管道接收的文本,并且将分隔后文本均作为参数

  • xargs常用选项

1
2
3
4
参数	    解释
-d 指定分隔符,不指定默认为空白字符
-p 先打印即将执行的命令并询问是否执行
-n 指定每次传输给后续命令的参数个数,不指定则全部传递
  • 示例

1
2
3
4
5
6
7
8
9
10
11
12
13
# 假设当前目录下有a.txt和b.txt,则该命令的含义就是 cat a.txt b.txt
ls *.txt | xargs cat

# 等价于 按顺序执行 cat a.txt 和 cat b.txt
ls *.txt | xargs -n 1 cat
# 上面的命令与下面的作用相同,-I{} 参数告诉 xargs 将标准输入的每一行作为参数传递给后面的命令,并将 {} 替换为每个参数。
ls *.txt | xargs -I{} cat {}

# 删除文件,执行前先打印命令并询问是否执行,输入y回车后可以执行,仅仅回车不执行
ls *.txt | xargs -p rm -rf

# 杀掉进程
ps -ef | grep 'test.sh' | grep -v 'grep'| awk '{print $2}' | xargs kill

文件描述符

  • 在 Linux 系统中,进程通过文件描述符来管理文件,文件描述符就是一个数字以及与之相关联的一堆数据

  • 一个进程打开一个文件,就会创建一个新的文件描述符,这个数字一般是自增的,进程如果关闭文件,这个描述符是可以重复利用的

  • 查看进程关联的文件描述符

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# 比如查看`mysqld`的文件描述符
# 先获得mysqld的PID,这里是823
$ ps aux | grep mysqld | grep -v "grep"
mysql 823 0.0 7.0 1653908 64080 ? Ssl 3月01 7:50 /usr/sbin/mysqld

# 进入如下目录
$ cd /proc/823/fd
# 列出PID为823的进程关联的文件描述符
$ ls
0 12 16 2 23 27 30 34 38 41 45 49 52 56 6 63 67 70 9
1 13 17 20 24 28 31 35 39 42 46 5 53 57 60 64 68 71
10 14 18 21 25 29 32 36 4 43 47 50 54 58 61 65 69 72
11 15 19 22 26 3 33 37 40 44 48 51 55 59 62 66 7 8

# 通过ls -l可以查看到实际关联的文件,这里对文件描述符按数字大小做了排序
$ ls -l | grep "^l" | sort -k 9n | head -n 5
lr-x------ 1 mysql mysql 64 3月 13 04:06 0 -> /dev/null
lrwx------ 1 mysql mysql 64 3月 13 04:06 1 -> socket:[16837]
lrwx------ 1 mysql mysql 64 3月 13 04:06 2 -> socket:[16837]
lrwx------ 1 mysql mysql 64 3月 13 04:06 3 -> /var/lib/mysql/aria_log_control
lr-x------ 1 mysql mysql 64 3月 13 04:06 4 -> /var/lib/mysql
  • 每一个进程启动的时候,都会默认打开三个文件,用0,1,2来当做他们的描述符

1
2
3
0 : 标准输入文件(stdin),代表标准输入,默认指键盘输入
1 : 标准输出文件(stdout),代表标准输出,标准输出就是命令的输出,默认指向终端屏幕
2 : 标准错误文件(stderr),代表错误输出,标准错误是命令错误信息的输出,默认指向终端屏幕
  • 进程操作所有文件的过程都是一样的,都是先打开一个文件,给他一个文件描述符,然后针对这个描述符进行读或者写

  • 在Linux里,键盘输入这种操作会变成一个文件操作,就类似普通的文件那样,这就是Linux的设计,一切皆是文件

  • 不仅仅键盘操作和屏幕打印是文件操作,网络连接读取数据等等,也都是文件操作,这些也都会产生文件描述符

  • 一个进程同时拥有的文件描述符是有上限的,这个上限可以设置。

  • 我们来解读下面这个命令的执行过程

    1
    cat test.txt | grep "hello"
    • 1.cat这个程序打开了test.txt这个文件,将其内容写入 1 这个文件(标准输出),也就是屏幕上
    • 2.管道符|的作用就是将前一个程序的 1 (标准输出)绑定到后一个程序的0(标准输入),这里就是将cat1绑定到grep0
    • 3.然后grep 这个程序就试图从 0 这个文件(标准输入)中读取数据,然后找到包含hello的行,然后把找到的行写入到 1 这个文件(标准输出)中,也就是屏幕上

设置文件描述符上限

  • 文件描述符上限设置分为三个限制级别:系统限制、用户限制、会话限制,最终的上限是这三个限制级别中最小的值

  • 查看及设置系统上限

1
2
3
4
5
6
7
8
# 查看
cat /proc/sys/fs/file-max
或者
sysctl fs.file-max

# 设置
echo fs.file-max=65535 >> /etc/sysctl.conf
sysctl -p # 立即生效
  • 查看及设置用户上限

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# 查看
cat /etc/security/limits.conf
# 设置格式为:
      username|@groupname type resource limit
      username|@groupname:设置需要被限制的用户名,组名前面加@和用户名区别。也可以用通配符*来做所有用户的限制。
      type:有 soft,hard 和 -,soft 指的是当前系统生效的设置值。hard 表明系统中所能设定的最大值。soft 的限制不能比hard 限制高。用 - 就表明同时设置了 soft 和 hard 的值
      resource: 为资源类型有多种。 nofile为最大打开文件数。
      例如:* - nofile 65535 将所有用户的最大打开文件数的soft和hard都设为65535
# 对/etc/security/limit.conf的修改会在新的会话期中生效
# 设置时一般在文件最后添加如下内容
root soft nofile 65535
root hard nofile 65535
* soft nofile 65535
* hard nofile 65535
  • 查看及设置会话上限 :默认继承自用户限制级别

1
2
3
4
5
6
7
# 查看
ulimit -Sn :查看会话期最大文件描述符soft限制
ulimit -Hn :查看会话期最大文件描述符hard限制,hard限制是soft的上限。

# 设置
# 用ulimit所做的修改在会话期结束后都将失效
ulimit -Sn 2046 :限制当前会话期内能打开的文件数为2046(如果其中一个进程打开了2046个,其他进程再打文件都将失败)
  • 文件描述符使用上限最大能设置多大呢?当然你可以把它往大了设,但是同时打开的文件描述符越多,内存开销就越大。那怎样设置一个合理的上限呢?一个经验算法是 256个fd 需4M内存。例如8G内存,8*1024/4*256=524288

  • 如果我们在一台机器上部署了自己的网络服务,我们只需按下面步骤修改就可以了:

1
2
3
4
5
6
7
8
  1. 计算 fdmax = 物理内存大小(m为单位) / 4 * 256  ,假设内存为8G,fdmax=524288
  2. 执行命令: echo fs.file-max=524288 >> /etc/sysctl.conf
  3. 执行命令: sysctl -p
  4. 执行命令: echo * soft nofile 524286 >> /etc/security/limits.conf
  5. 执行命令: echo * hard nofile 524287 >> /etc/security/limits.conf
  6. 结束当前会话期
  7. 启动新的会话
  8. 启动我们的服务

重定向操作符

  • 所谓重定向,其含义就是将不同的文件描述符重新定向到其它文件描述符

  • 常用的重定向操作符有如下几种:

    • < :将文件作为命令的标准输入
    1
    2
    cat < test.txt
    mysql -uroot -p < databases.sql
    • <<< 操作符可以在许多场景下用于将字符串作为标准输入传递给命令
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    # 打印字符串
    cat <<< "Hello World"

    # 将字符串 "Hello World" 传递给 wc -c 命令,用于统计字符数
    wc -c <<< "Hello World"

    # 向管道命令传递字符串,将字符串 "This is a line containing the pattern" 传递给 grep 命令,用于搜索包含指定模式的行。
    ## 单行过滤
    grep "pattern" <<< "This is a line containing the pattern"
    ## 多行过滤
    grep Line <<< "$(printf "Line 1\nLine 2\nLine 3\n")"

    # 向交互式命令传递输入,这里将字符串 "2 + 2" 传递给 bc 命令,用于进行数学计算
    bc <<< "2 + 2"
    ## 计算了正弦函数 sin(1) 和余弦函数 cos(1) 的和
    bc -l <<< "s(1) + c(1)"

    # 向循环结构传递输入,printf 命令生成了一个包含换行符的字符串,并使用 <<< 将其传递给 while read 循环。read 命令会按行读取输入,并将每行赋值给变量 line。
    while read -r line; do
    echo "Line: $line"
    done <<< "$(printf "Line 1\nLine 2\nLine 3\n")"
    • > :将命令的输出结果输出到指定文件中,就是将标准输出重定向,且覆盖原文件内容
    1
    2
    3
    4
    5
    6
    # 以下两个命令的作用是一样的,都是将标准输出重定向到文件
    cat test.txt | grep "hello" > result.txt
    cat test.txt | grep "hello" 1> result.txt

    # 标准输出与标准错误都重定向到文件
    sh test.sh 1> run.log 2> error.log
    • >> :将命令的输出结果以 追加 的方式输出到指定文件中
    1
    2
    3
    4
    5
    6
    # 以下两个命令的作用是一样的,都是将标准输出重定向到文件
    cat test.txt | grep "hello" >> result.txt
    cat test.txt | grep "hello" 1>> result.txt

    # 标准输出与标准错误都重定向到文件
    sh test.sh >> run.log 2>> error.log
    • >& :将命令的输出结果或者一个文件描述符重新定向到另一个文件描述符
    1
    2
    3
    4
    5
    6
    7
    8
    9
    # 以下两个命令的作用是一样的,都是将标准输出重定向到文件,标准错误重定向且等同于标准输出
    cat test.txt | grep "hello" > result.txt 2>&1
    cat test.txt | grep "hello" 1> result.txt 2>&1

    # 标准输出重定向到文件,标准错误重定向且等同于标准输出
    sh test.sh >> run.log 2>&1

    # 标准输出重定向到空设备文件,也就是不输出也不显示任何信息,标准错误重定向且等同于标准输出,也就是标准错误也重定向到空设备中
    sh test.sh 1>/dev/null 2>&1
  • 使用重定向操作符时的注意事项

    1
    2
    3
    1.标准输入0、标准输出1、标准错误2 需要分别重定向,一个重定向只能改变它们其中一个。
    2.文件描述符在重定向符号左侧时可以省略。
    3.文件描述符与重定向符号之间不能有空格。

小贴士
我在mac系统习惯在命令行中使用code file命令打开某个文件,但是每次使用code命令时都会报告一个错误信息

1
[1025/160343.890943:ERROR:codesign_util.cc(109)] SecCodeCheckValidity: Error Domain=NSOSStatusErrorDomain Code=-67062 "(null)" (-67062)

这个错误信息表明在使用MacOS系统中的代码签名工具SecCodeCheckValidity时发生了问题,导致无法验证代码的有效性。

其实这个错误不影响使用,只是看着不美观,解决方法也很简单,在shell启动文件(例如:~/.zshrc)中加上如下配置即可

1
2
# 不提示任何错误信息
alias code="code 2> /dev/null"

我在网上看到其它解决方法是直接修改code的源码,但是这个修改方式有一个问题,就是修改源码后每次升级code都会覆盖掉,所以还是直接在shell启动文件中配置一个别名来屏蔽错误信息更一劳永逸。