Akemi

高效使用Linux-执行命令的不同方式

2025/02/28

有条件列表、无条件列表

1
2
3
4
5
6
7
这个很好理解
有条件列表:前一个命令执行成功或失败,才会执行
git add . && git commit -m 'test' && git push
[ $? -eq 0 ] || echo "commmand run fail"

无条件列表:用分号隔开的命令
for ((i=10; i>5; i--)); do echo $i; done

命令替换

指的是将字符串替换为命令

1
2
3
4
5
6
7
8
$(xxxx)
执行括号内的命令,并将命令替换为输出

也可以使用``反引号,但是不够清晰
推荐使用$()的方式,并且便于嵌套

mkdir john
mv $(grep -l "Artist:John" *.txt) John

进程替换

进程替换是 Shell 提供的一种高级重定向机制,允许将一个命令的输入或输出“伪装”成一个临时文件的路径

1.Shell 会动态创建一个临时命名管道(FIFO)或直接通过 /dev/fd 生成一个匿名文件描述符。

例如,<(ls) 会生成类似 /dev/fd/63 的路径。

2.启动子进程:

在后台启动 command(如 ls),并将其输出绑定到这个临时文件描述符。

3.传递路径给主命令:

将临时文件描述符的路径(如 /dev/fd/63)作为参数传递给主命令(如 diffpaste)。

4.数据流处理:

主命令像读取普通文件一样从这个路径读取数据,实际读取的是子进程的动态输出。

1
2
3
4
5
6
diff <(ls dir1) <(ls dir2)

Shell 创建两个临时文件描述符 /dev/fd/63 和 /dev/fd/64。
启动 ls dir1 和 ls dir2,将它们的输出分别绑定到这两个描述符。
运行 diff /dev/fd/63 /dev/fd/64,比较两个目录的内容。
比较完成后,临时描述符自动关闭

文件描述符的本质

文件描述符(File Descriptor,简称 fd)是操作系统用来标识和管理已打开文件或 I/O 资源的整数句柄。每个进程独立维护自己的文件描述符表。

  • 标识资源:fd 不直接操作文件,而是指向内核中打开的文件、管道、套接字等资源。
  • 动态分配:进程每打开一个资源,内核会分配一个最小的可用 fd(通常从 3 开始)。
1
2
3
4
5
6
# 查看进程替换的临时文件路径
echo <(echo "Hello") # 输出类似 "/dev/fd/63"
cat <(echo "Hello") # 输出 "Hello"

# 查看文件描述符类型
ls -l /dev/fd/63 # 显示类型为管道(pipe)

当使用进程替换(如 <(command))时:

  1. Shell 会为 command 的输出分配一个新的 fd(例如 63)。
  2. 生成一个路径 /dev/fd/63,指向这个 fd。
  3. 主命令通过这个路径读取数据,实际是通过 fd 直接读取 command 的输出流。

底层技术:命名管道(FIFO)

在早期系统中,进程替换通过创建命名管道(FIFO)实现:

1
2
3
4
# 手动模拟进程替换
mkfifo /tmp/fifo1
ls dir1 > /tmp/fifo1 & # 后台写入数据到管道
diff /tmp/fifo1 <(ls dir2)

进程替换案例

1
2
3
4
5
6
7
8
9
10
11
用进程替换替代临时文件:
比较两个动态生成的输出
diff <(ls /path/to/dir1) <(ls /path/to/dir2)

同时处理多个数据流:
比较本地文件与远程文件内容
diff local_file.txt <(curl -s http://example.com/remote_file.txt)

将输出发送到多个进程(不常用)
将日志同时传给统计工具和过滤器
tee >(grep "ERROR" > errors.log) >(wc -l > error_count.log) < app.log

使用bash执行命令

这个很好理解,相当于新创建一个shell,然后让命令在子shell中运行

1
2
3
bash -c 'echo "New Log" > /var/log/test.log'
或者传入一个脚本
sh "test.sh var1 var2“

通过管道执行命令

举一个多正则匹配的例子

1
2
3
4
5
6
7
8
9
10
列出名称大于3个字母的文件
ls -1 ??*

通过字符串操作生成命令
第一个正则表达式,取出第一个字符 ^\(.\)
第二个正则表达式,取出剩余字符 \(.*\)$

将a-z开头的文件夹都放入名称为a-z的文件夹中
mkdir {a..z}
ls -1 ??* | sed 's#^\(.\)\(.*\)$'#mv \1\2 \1#'

通过ssh执行命令

1
2
3
4
5
6
ssh servera ls > outfile # 在本机创建文件
ssh servera "ls > outfile" # 在远程主机创建文件

echo "ls > outfile" | ssh servera # 默认情况下servera会分配伪终端
echo "ls > outfile" | ssh -T servera # 不启用伪终端,非交互式模式运行
echo "ls > outfile" | ssh servera bash # 强制使用 Bash,会调用shell特性

xargs与xargs安全性

xargs初级知识就略过了

不展开

1
2
3
4
5
6
7
8
9
在find和xargs结合使用时
不要单独使用xargs
要使用:
find .... -printO | xargs -O

-I 使用占位符控制位置
ls | xargs -I ABC echo ABC is good
apple is good
banana is good

显式子shell

cat package.tar.gz | (mkdir -p /tmp/other && ch /tmp/other && tar xzvf -)

读取tar包数据,放到后面新创建的目录中解压

tar czf - dir1 | (cd /tmp/dir2 && tar xvf -)

以压缩的方式将文件从dir1转到dir2

进程替换

exec ,将正在运行的shell替换成指定的命令,并在执行完成后退出

  • 节约资源

不需要再启动一个进程

有些脚本如果要执行上百上千次,那么在最后一条命令时使用exec,能够节约相当的资源

  • 为当前shell分配新的标准输入、标准输出和错误
1
2
3
4
5
6
7
8
9
10
11
#!/bin/bash
echo a >> /tmp/file
echo b >> /tmp/file
echo c >> /tmp/file

与下面同等效果
#!/bin/bash
exec > /tmp/file
echo a
echo b
echo c
CATALOG
  1. 1. 有条件列表、无条件列表
  2. 2. 命令替换
  3. 3. 进程替换
    1. 3.1. 文件描述符的本质
    2. 3.2. 底层技术:命名管道(FIFO)
    3. 3.3. 进程替换案例
  4. 4. 使用bash执行命令
  5. 5. 通过管道执行命令
  6. 6. 通过ssh执行命令
  7. 7. xargs与xargs安全性
  8. 8. 显式子shell
  9. 9. 进程替换