play shell (OnGoing...)

play shell. 本篇关于 shell 相关概念的不完全整理,包括其中各种奇奇怪怪的符号含义,运行机理等。因为 shell 和 linux 系统密不可分,所以内容会比较杂。

如何阅读 man 手册

man 命令手册页通常使用 pager 工具显示,常用的 pager 工具有 lessmore,默认使用 less
如果忘记某个命令名,可以使用 man -k 关键字 来搜索,如 man -k network

手册页惯用段名

段名描述
Name命令的名字和简短描述
Synopsis命令语法
Configuration命令配置信息
Description命令描述
Options命令选项
Exit Status命令退出状态
Return Value命令返回值
Errors命令错误信息
Environment命令环境变量
Files命令相关文件
Version命令版本
Conforming To遵循的命名标准
Notes注意事项以及资料
Examples用法示例
Copyright版权信息
See Also与该命令类似的相关命令

手册页的节号

节号所涵盖的内容
1可执行程序或 shell 命令
2系统调用(内核函数)
3库函数(通常是 C 标准库函数)
4特殊文件(通常是 /dev 中的设备文件)
5文件格式和约定(如 /etc/passwd)
6游戏
7惯例和协议(如 IP、Ethernet、ASCII 码等)
8系统管理员命令和守护进程(通常只有 root 用户才能运行)
9内核源代码(routine)

不同发行版节的编号也可能不同,但标准节号如上

子 shell 与进程列表

登入 shell CLI 时是一个父 shell, 当执行 zsh 时会创建一个子 shell,子 shell 会继承父 shell 的环境变量,但是父 shell 无法获取子 shell 的环境变量。
例如当我连接到某个服务器时,执行多次 zsh 命令,每次执行都会创建一个子 shell,可通过 ps 命令查看进程列表。

1
2
3
4
5
6
7
╭─niku at vps in ~ 24-06-27 - 11:49:22
╰─○ ps --forest -f
UID          PID    PPID  C STIME TTY          TIME CMD
niku       25644   25643  0 11:31 pts/0    00:00:00 -zsh
niku       25883   25644  2 11:49 pts/0    00:00:00  \_ zsh
niku       25923   25883  4 11:49 pts/0    00:00:00      \_ zsh
niku       25962   25923  0 11:49 pts/0    00:00:00          \_ ps --forest -f

执行多个命令 () 的影响

执行多个命令可用 ; 连接,表示多个命令依次执行,例如 pwd; ls; cd /etc; pwd,但如果在命令外添加圆括号 (pwd; ls; cd /etc; pwd) 则会作为进程列表运行,生成一个子 shell。
但使用花括号 { command; } 的方式进行命令分组则不会生成子 shell。
可通过 echo $ZSH_SUBSHELL 验证是否创建了子 shell, 0 表示未创建子 shell,1 表示创建了子 shell。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
╭─niku at vps in ~ 24-06-27 - 11:57:43
╰─○ pwd; ls; cd /etc; pwd; ls; echo $ZSH_SUBSHELL
/home/niku
container
/etc
adduser.conf            console-setup   deluser.conf            fstab        hosts.allow      ld.so.conf.d    magic           netconfig      perl        rc5.d           shadow     sudo_logsrvd.conf  w3m
adjtime                 containerd      dhcp                    gai.conf     hosts.deny       letsencrypt     magic.mime      network        profile     rc6.d           shadow-    sv                 wgetrc
alternatives            cron.d          dictionaries-common     ghostscript  init.d           libaudit.conf   mailcap         networks       profile.d   rc.local        shells     sysctl.conf        X11
apparmor                cron.daily      discover.conf.d         groff        initramfs-tools  libnl-3         mailcap.order   nftables.conf  protocols   rcS.d           skel       sysctl.d           xattr.conf
apparmor.d              cron.hourly     discover-modprobe.conf  group        inputrc          libpaper.d      manpath.config  nginx          python3     reportbug.conf  ssh        systemd            xdg
apt                     cron.monthly    docker                  group-       iproute2         locale.alias    mime.types      nsswitch.conf  python3.11  resolv.conf     ssl        terminfo           zsh
bash.bashrc             crontab         dpkg                    grub.d       issue            locale.gen      mke2fs.conf     opt            qemu        rmt             subgid     timezone
bash_completion         cron.weekly     e2scrub.conf            gshadow      issue.net        localtime       modprobe.d      os-release     ranger      rpc             subgid-    tmpfiles.d
bash_completion.d       cron.yearly     emacs                   gshadow-     kernel           logcheck        modules         pam.conf       rc0.d       rsyslog.d       subuid     ucf.conf
bindresvport.blacklist  dbus-1          environment             gss          kernel-img.conf  login.defs      modules-load.d  pam.d          rc1.d       runit           subuid-    udev
binfmt.d                debconf.conf    ethertypes              host.conf    ldap             logrotate.conf  monit           papersize      rc2.d       security        sudo.conf  ufw
ca-certificates         debian_version  fail2ban                hostname     ld.so.cache      logrotate.d     motd            passwd         rc3.d       selinux         sudoers    update-motd.d
ca-certificates.conf    default         fonts                   hosts        ld.so.conf       machine-id      mtab            passwd-        rc4.d       services        sudoers.d  vim
0

╭─niku at vps in ~ 24-06-27 - 11:58:37
╰─○ (pwd; ls; cd /etc; pwd; ls; echo $ZSH_SUBSHELL)
/home/niku
container
/etc
adduser.conf            console-setup   deluser.conf            fstab        hosts.allow      ld.so.conf.d    magic           netconfig      perl        rc5.d           shadow     sudo_logsrvd.conf  w3m
adjtime                 containerd      dhcp                    gai.conf     hosts.deny       letsencrypt     magic.mime      network        profile     rc6.d           shadow-    sv                 wgetrc
alternatives            cron.d          dictionaries-common     ghostscript  init.d           libaudit.conf   mailcap         networks       profile.d   rc.local        shells     sysctl.conf        X11
apparmor                cron.daily      discover.conf.d         groff        initramfs-tools  libnl-3         mailcap.order   nftables.conf  protocols   rcS.d           skel       sysctl.d           xattr.conf
apparmor.d              cron.hourly     discover-modprobe.conf  group        inputrc          libpaper.d      manpath.config  nginx          python3     reportbug.conf  ssh        systemd            xdg
apt                     cron.monthly    docker                  group-       iproute2         locale.alias    mime.types      nsswitch.conf  python3.11  resolv.conf     ssl        terminfo           zsh
bash.bashrc             crontab         dpkg                    grub.d       issue            locale.gen      mke2fs.conf     opt            qemu        rmt             subgid     timezone
bash_completion         cron.weekly     e2scrub.conf            gshadow      issue.net        localtime       modprobe.d      os-release     ranger      rpc             subgid-    tmpfiles.d
bash_completion.d       cron.yearly     emacs                   gshadow-     kernel           logcheck        modules         pam.conf       rc0.d       rsyslog.d       subuid     ucf.conf
bindresvport.blacklist  dbus-1          environment             gss          kernel-img.conf  login.defs      modules-load.d  pam.d          rc1.d       runit           subuid-    udev
binfmt.d                debconf.conf    ethertypes              host.conf    ldap             logrotate.conf  monit           papersize      rc2.d       security        sudo.conf  ufw
ca-certificates         debian_version  fail2ban                hostname     ld.so.cache      logrotate.d     motd            passwd         rc3.d       selinux         sudoers    update-motd.d
ca-certificates.conf    default         fonts                   hosts        ld.so.conf       machine-id      mtab            passwd-        rc4.d       services        sudoers.d  vim
1

子 shell 常见用法

  1. 后台模式 后台模式运行 command &: 例如 sleep 30&,会在后台运行 sleep 30 秒。通过 jobs 命令可以查看后台运行的作业,执行完成后会输出 done。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    
    ╭─niku at vps in ~ 24-06-27 - 12:14:36
    ╰─○ sleep 30&
    [1] 26260
    ╭─niku at vps in ~ 24-06-27 - 12:14:40
    ╰─○ jobs -l
    [1]  + 26260 running    sleep 30
    ╭─niku at vps in ~ 24-06-27 - 12:14:44
    ╰─○
    [1]  + 26260 done       sleep 30
  2. 进程列表置入后台 可以在子 shell 中进行多进程处理,这样终端不再和子 shell 的 I/O 绑定在一起。
    例如通过将 tar 命令置入后台,可以在后台执行压缩操作,不会阻塞终端,从而可以方便管理员继续执行其他操作。
    1
    
     (tar -czf /tmp/test.tar.gz /tmp/test) &
  3. 协程 coproc sleep 10 该命令同样生成一个子 shell,同时可以为该作业命名,coproc my_job { sleep 10; }注意花括号两侧必须有空格

外部命令和内建命令

  • 外部命令:存在于 shell 之外的命令,不属于 shell 的一部分。例如 ps 等一系列命令,通常位于 /bin/usr/bin 目录下。外部命令执行时需要创建子 shell。
  • 内部命令:已经和 shell 编译成一体的命令,不需要创建子 shell。例如 cdechoexit 等。
  • 可以通过 type 命令判断命令是内部命令还是外部命令。

环境变量

  • 全局环境变量 全局环境变量是在 shell 启动时就加载的环境变量,对于所有子 shell 可见,可以通过 printenvenv 命令查看。
    • 设置全局变量可以通过 export 命令,例如 export my_var=xxx(子 shell 中修改同名全局变量不会影响父 shell)。
    • 删除全局变量可以通过 unset 命令,例如 unset my_var。同样子进程中删除全局变量不会影响父 shell。
  • 局部环境变量 局部环境变量是在当前 shell 中定义的环境变量,只对当前 shell 可见,set 命令可以查看局部变量,全局变量和用户自己定义的变量。
  • 用户自定义环境变量 启动 shell 后,用户可以自定义环境变量,例如 my_value="hello world"echo $my_value 可以查看,如果此时生成子 shell,该变量在子 shell 中不可用。(局部变量建议使用小写表示。系统变量全大写,变量名、等号和值之间没有空格,如果有空格 shell 会将其视为单独命令)。

常见的默认环境变量

变量名描述
HOME当前用户的家目录
PATHshell 查找命令的路径
BASHbash shell 的路径
LANG语言环境
HOSTNAME主机名
PWD当前工作目录
RANDOM随机数

PATH 环境变量

PATH 环境变量是 shell 查找命令的路径,可以通过 echo $PATH 查看,如果一些程序执行路径没有添加到 PATH 中则只能使用绝对变量调用。

可以联系到常见的 shell 脚本起手式:#!/usr/bin/env bash env 会在环境变量 PATH 中查找 bash 这样的脚本具有更好的可移植性。

  • 追加新的路径到 PATH 中可以使用 export PATH=$PATH:/home/niku/myscripts.

环境变量定位&持久化

  • 环境变量定位,登入 Linux 系统时 shell 启动加载的文件:
    • /etc/profile:系统级别的环境变量配置文件,对所有用户生效。
    • ~/.profile:用户级别的环境变量配置文件,对当前用户生效。
    • ~/.bash_profile:用户级别的环境变量配置文件,对当前用户生效。
    • ~/.bashrc:用户级别的 bash 配置文件,对当前用户生效。
    • ~/.zshrc:用户级别的 zsh 配置文件,对当前用户生效。

目录规范:/etc/profile.d目录中用来放不同的配置文件,profile 脚本执行时会循环处理 .d 目录中的文件。

  • 环境变量持久化
    • 全局系统变量:慎用 /etc/profile 放置系统变量发行版升级该文件可能被更新,建议使用 /etc/profile.d 目录创建一个 .sh 文件并分类存放。
    • 个人环境变量:建议使用 ~/.bashrc~/.zshrc 文件,将个人环境变量写入该文件中,这样每次登入 shell 时都会加载。

数组变量

某个变量设置多个值:my_array=(value1 value2 value3),通过 ${my_array[0]} 可以获取数组中的值。

  • echo ${my_array[*]}:输出数组中的所有值。

常见符号含义

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
$   # 引用
''  # 强引用,优先级大于 $ 
""  # 若引用,优先级小于 $
!!  # 上一条命令,并执行

() # 子 shell,会创建一个子 shell
{} # 命令分组,不会创建子 shell
[] # test 表达式,例如 [ -e /etc/passwd ]
(( 算术表达式 )) # 内部'<' '>' 不需要转义
[[ $var == "test" ]] # 字符串比较、模式匹配

-eq:等于
-ne:不等于
-le:小于等于
-ge:大于等于
-lt:小于
-gt:大于

=~       # 左侧是字符串,右侧是一个模式,判断左侧的字符串能否被右侧的模式所匹配:但是必须在[[]]中执行模式匹配。
!=, <>   # 不等于
>        # 输出重定向
>>       # 代表追加重定向
<        # 输入重定向 command < inputfile
<<       # 内联输入重定向, 例如 cat << EOF

指令解析

>/dev/null 2>&1

该指令常用于隐藏上一条指令的输出。

  1. > 代表重定向到哪里,例如:echo “123” > /home/123.txt
  2. /dev/null 代表空设备文件
  3. 2 表示stderr标准错误
  4. & 表示等同于的意思,2>&1,表示2的输出重定向等同于1
  5. 1 表示stdout标准输出,系统默认值是1,所以">/dev/null"等同于 “1>/dev/null”
  6. &> 表示标准输出和标准错误一起重定向
1
2
3
# 以下两条命令等价。
ls /home > /dev/null 2>&1
ls /home &> /dev/null

exit code

bash 参考 使用 $? 命令可以查看上一条命令的退出状态。 脚本中可以通过 exit 命令设置退出状态码,例如 exit $var 将退出状态指定为一个变量值,需要注意退出码最大到 255。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
状 态 码         描 述
Exit code 0        Success
Exit code 1        General errors, Miscellaneous errors, such as "divide by zero" and other impermissible operations
Exit code 2        Misuse of shell builtins (according to Bash documentation)        Example: empty_function() {}
126           命令不可执行
127           没找到命令
128           无效的退出参数
128+x          与Linux信号x相关的严重错误
130           通过Ctrl+C终止的命令
255           正常范围之外的退出状态码

shell 语法

命令替换

将命令输出赋值给变量,需要注意命令替换会创建子 shell 运行指定的命令。

1
2
3
4
my_var=$(ls)
my_var=`ls`

echo $my_var // 输出 ls 命令的结果

控制结构

if-then 语句

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
# if-then
if command      # if 后会根据命令返回的退出码为 0(成功),则执行 then 后的命令
then
    command 1
    command 2
    ...
fi

# if-then-else
if command1
then
    command2
else
    command3
fi

# elif
if command1
then
    command2
elif command3
then
    command4
fi

# 简化写法
if command1; then # 与上面等价
    command2
fi

if command1; then
    command2
else
    command3
fi

case 语句:

1
2
3
4
5
6
7
8
case $var in
    pattern1 | pattern2)
        command1
        command2
        ;;
    pattern3) command3;;
    *) defaultcommand;;
esac

test 命令

若 if 需要判断非命令退出状态的条件,可以使用 test 命令。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
# 语法格式
test condition
# 或者使用 [],大部分 shell 支持,前后需要空格
[ condition ]

# example
my_var="test"
if test $my_var # 判断变量是否为空
then
    echo "yes"
fi

test 也可做数值条件判断,例如:

1
2
3
4
5
6
[ $my_var -eq 0 ] # 判断变量是否等于 0
[ $my_var -ne 0 ] # 判断变量是否不等于 0
[ $my_var -lt 0 ] # 判断变量是否小于 0
[ $my_var -gt 0 ] # 判断变量是否大于 0
[ $my_var -le 0 ] # 判断变量是否小于等于 0
[ $my_var -ge 0 ] # 判断变量是否大于等于 0

字符串比较:

1
2
3
4
[ $my_var = "test" ] # 判断变量是否等于 test
[ $my_var != "test" ] # 判断变量是否不等于 test
[ -z $my_var ] # 判断变量长度是否为0
[ -n $my_var ] # 判断变量长度是否不为0
注意
  • 比较字符串大小时大于号和小于号需要转义,否则将被 shell 视为重定向符,例如 [ "a" \< "b" ]
  • 比较方式和 sort 命令不同,sort 命令默认是按照字典序排序,而 test 命令是按照 ASCII 码排序。

文件比较:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
[ -f $file ] # 判断文件是否存在
[ -d $file ] # 判断文件是否是目录
[ -e $file ] # 判断文件是否存在
[ -r $file ] # 判断文件是否可读
[ -w $file ] # 判断文件是否可写
[ -x $file ] # 判断文件是否可执行
[ -s $file ] # 判断文件是否为空
[ -L $file ] # 判断文件是否是软链接
[ -O $file ] # 判断文件是否属于当前用户
[ -G $file ] # 判断文件是否属于当前用户组
[ $file1 -nt $file2 ] # 判断文件1是否比文件2新
[ $file1 -ot $file2 ] # 判断文件1是否比文件2旧
[ $file1 -ef $file2 ] # 判断文件1和文件2是否是同一个文件

复合条件:

1
2
3
[ condition1 ] && [ condition2 ] # 与
[ condition1 ] || [ condition2 ] # 或
[ ! condition1 ] # 非

推荐阅读

Linux Shell Scripting Tutorial Linux 命令行与 shell 脚本编程大全

0%