跳转至

Bash 的使用技巧

约 2447 个字 362 行代码 26 张图片 预计阅读时间 13 分钟

变量

使用、设置、取消

# 使用
echo $变量名
# 或
echo ${变量名}

# 设置(等号两边不能加空格)
变量名=
# 取消
unset 变量名

设置规则

  • 变量名开头不能是数字
  • 通常系统变量为大写,自行设置变量一般用小写(建议)
  • 变量值可以用双引号或单引号包裹,以在其中包含空格等字符
  • 双引号内的特殊字符可以保存原有特性
  • 单引号内的特殊字符仅为一般字符,不能再套单引号
  • 特殊字符的转义用反斜杠
  • 需要借别的命令提供信息时,可以使用 `命令`$(命令)(建议)
  • 需要使用其他变量内容,或者是扩增自身内容,可以使用 ${变量名}(建议)或 "$变量名"

设置变量 - 例

ding@ding-server:~$ echo "The $(uname) core version is: $(uname -r)"
The Linux core version is: 5.4.0-109-generic
ding@ding-server:~$ echo 'The $(uname) core version is: $(uname -r)'
The $(uname) core version is: $(uname -r)
ding@ding-server:~$ name=Ding\ Junyao
ding@ding-server:~$ echo $name
Ding Junyao
ding@ding-server:~$ name=${name}\'s\ name
ding@ding-server:~$ echo $name
Ding Junyao's name
ding@ding-server:~$ name='This is '${name}
ding@ding-server:~$ echo $name
This is Ding Junyao's name

变量作用范围

环境变量的作用范围为当前环境。

通过前面的流程设置的变量的作用范围为当前进程,在子进程中无法使用。如果需要在子进程中使用,需要设置为环境变量:

export 变量名
直接定义环境变量:
export 变量名=

例:

# 在父进程
ding@ding-server:~$ echo $name
This is Ding Junyao's name
ding@ding-server:~$ bash
# 至此离开父进程,进入子进程 1
ding@ding-server:~$ echo $name

ding@ding-server:~$ exit
exit
# 至此离开子进程 1,到父进程
ding@ding-server:~$ echo $name
This is Ding Junyao's name
ding@ding-server:~$ export name
ding@ding-server:~$ bash
# 至此离开父进程,进入子进程 2
ding@ding-server:~$ echo $name
This is Ding Junyao's name

经此前 export 配置,这时候的变量仅在当前登录终端有效。如果想要永久有效,需要更改配置文件,在其中添加设置变量的指令。

Bash 的 Login Shell 的配置文件结构如下图,需按需更改对应的配置文件。这些配置文件均为 Shell 脚本。

Login Shell 的配置文件读取过程(bash)
Login Shell 的配置文件读取过程(bash)

配置后,下次登录时即生效。如果想立即生效,执行:

1
2
3
source 配置文件路径
# 或
. 配置文件路径

查看目前的环境变量

ding@ding-server:~$ env
SHELL=/bin/bash
JAVA_HOME=/usr/lib/jvm/java-11-openjdk-amd64/
PWD=/home/foxconn
LOGNAME=foxconn
HOME=/home/foxconn
LANG=en_US.UTF-8
LESSOPEN=| /usr/bin/lesspipe %s
USER=foxconn
PATH=/usr/lib/jvm/java-11-openjdk-amd64//bin:/usr/lib/jvm/java-11-openjdk-amd64//bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games:/usr/local/games:/snap/bin
DBUS_SESSION_BUS_ADDRESS=unix:path=/run/user/1000/bus
SSH_TTY=/dev/pts/0
_=/usr/bin/env
...
1
2
3
4
5
set
PS1='\[\e]0;\u@\h: \w\a\]${debian_chroot:+($debian_chroot)}\[\033[01;32m\]\u@\h\[\033[00m\]:\[\033[01;34m\]\w\[\033[00m\]\$ '
PS2='> '
PS4='+ '
...

? - 上一个命令的返回值

ding@ding-server:~$ ls
 0507                           a.txt
...
foxconn@hcm0153:~$ echo $?
0
foxconn@hcm0153:~$ ls 11.txt
ls: cannot access '11.txt': No such file or directory
foxconn@hcm0153:~$ echo $?
2
foxconn@hcm0153:~$ echo $?
0

一般返回值为 0 表示命令执行成功,非 0 表示执行失败

别名

1
2
3
4
5
6
7
8
# 设置别名(虽然可以用双引号包裹,但还是建议用单引号)
alias 别名='命令'

# 查看目前使用中的别名
alias

# 删除别名
unalias 别名

别名的作用范围和变量一样,如果想永久保存别名,同样需要更改配置文件。

每次删除时都要进行确认

alias rm='rm -i'

查看别名

1
2
3
4
5
6
ding@ding-server:~$ alias
alias grep='grep --color=auto'
alias l='ls -CF'
alias la='ls -A'
alias ll='ls -alF'
alias ls='ls --color=auto'

一条危险的命令

alias cd='rm -rf'

连续执行多条命令

多个命令之间用 ; 连接,表示无条件分步执行。

&&|| 有“且”和“或”的意义。

连续执行两条命令的连接符和流程图
连续执行两条命令的连接符和流程图

1
2
3
4
5
6
7
8
# 如果目录不存在则新建
ls 目录路径 || mkdir -p 目录路径

# 软件包安装成功才运行程序
sudo apt install 软件包 && 程序

# 尝试新建文件(文件所在目录可能不存在)
ls 目录路径 || mkdir -p 目录路径 && touch 文件路径

假设判断式

判断文件是否存在,如存在打印 "exist",否则打印 "not exist"。

答案是:

ls 文件 && echo "exist" || echo "not exist"

如果写成了

ls 文件 || echo "not exist" && echo "exist"

则如果文件不存在:

  1. 第一条语句返回非 0 值
  2. 执行第二条语句,返回 0
  3. 执行第三条语句

结果如下:

1
2
3
ls: cannot access '文件': No such file or directory
not exist
exist

格式

命令1 && 命令2 || 命令3

其中命令 2、3 会使用肯定成功的命令。

等价于:

假设判断式等价流程图
假设判断式等价流程图

流程图如下:

假设判断式流程图
假设判断式流程图

如果命令 1 返回值为 0(成功):

命令 1 返回值为 0 时的流程图
命令 1 返回值为 0 时的流程图

如果命令 1 返回值非 0(失败):

命令 1 返回值非 0 时的流程图
命令 1 返回值非 0 时的流程图

数据流重定向

数据流分类

数据流分类
数据流分类

各部分示例

数据流各部分示例
数据流各部分示例

将标准输出重定向到文件

一般来说是将内容输出到文件。

命令 > 路径
命令 >> 路径

>>> 的差别:如果文件已存在,> 会清空原有内容,>> 会在原有内容之后累加。

例:

ding@ding-server:~$ ls -al /
total 4194436
drwxr-xr-x  21 root root       4096 Apr 13 15:25 .
drwxr-xr-x  21 root root       4096 Apr 13 15:25 ..
lrwxrwxrwx   1 root root          7 Feb 23 16:49 bin -> usr/bin
...
ding@ding-server:~$ ls -al / > test.txt
ding@ding-server:~$ cat test.txt
total 4194436
drwxr-xr-x  21 root root       4096 Apr 13 15:25 .
drwxr-xr-x  21 root root       4096 Apr 13 15:25 ..
lrwxrwxrwx   1 root root          7 Feb 23 16:49 bin -> usr/bin
...
ding@ding-server:~$ ls -al / >> test.txt
ding@ding-server:~$ cat test.txt
total 4194436
drwxr-xr-x  21 root root       4096 Apr 13 15:25 .
drwxr-xr-x  21 root root       4096 Apr 13 15:25 ..
lrwxrwxrwx   1 root root          7 Feb 23 16:49 bin -> usr/bin
...
total 4194436
drwxr-xr-x  21 root root       4096 Apr 13 15:25 .
drwxr-xr-x  21 root root       4096 Apr 13 15:25 ..
lrwxrwxrwx   1 root root          7 Feb 23 16:49 bin -> usr/bin
...
...

重定向到设备

重定向到设备时,一般不要用累加符号。

传到另一个终端:

传到另一个终端
传到另一个终端

传到黑洞设备:

命令 > /dev/null

标准错误输出重定向

命令 2> 路径
命令 2>> 路径

# 将标准输出和标准错误输出放到不同的地方(> 可以按需换成 >>)
命令 > 标准输出路径 2> 标准错误输出路径

# 将标准输出和标准错误输出放到相同的地方,不能使用上面的方法,会导致交叉写入,造成次序混乱;用下面的方法:
命令 > 路径 2>&1    # 较常用;注意顺序
# 或
命令 &> 路径

例:

标准错误输出重定向 - 例
标准错误输出重定向 - 例

标准输入重定向

将某内容传入命令的标准输入:

命令 < 路径

对命令的标准输入传入一段内容,自定义结束符号(默认为 Ctrl + D):

1
2
3
4
命令 << 结束符号
> 内容
> 内容...
> 结束符号

前面的符号是在终端输入时自动添加的,不是输入内容;如果写脚本,直接写内容和结束符号就行了。

例:

标准输入重定向 - 例
标准输入重定向 - 例

管道

形式

1
2
3
命令1 | 命令2
命令1 | 命令2 | 命令3
...

管道示意图
管道示意图

标准错误输出与管道

当需要传递标准错误输出时,可以利用重定向。

将标准输出和标准错误输出一并传入下一个命令

命令1 2>&1 | 命令2

将标准输出和标准错误输出一并传入下一个命令 - 示意图
将标准输出和标准错误输出一并传入下一个命令 - 示意图

忽略标准输出,只将标准错误输出传入下一个命令

命令1 2>&1 > /dev/null | 命令2
# 注意顺序

忽略标准输出,只将标准错误输出传入下一个命令 - 示意图
忽略标准输出,只将标准错误输出传入下一个命令 - 示意图

例:

标准错误输出与管道 - 例
标准错误输出与管道 - 例

grep - 输出包含要找的文字所在的行

1
2
3
grep [选项] 要找的文字 文件
... | grep [选项] 要找的文字
# 支持标准输入的命令都有类似的形式,后续如无特殊情况不再写出

选项:

  • -c:输出符合条件的行的数量
  • -i:忽略大小写
  • -n:在符合条件的行前输出行号
  • -v:输出不符合条件的行
  • --color=auto:高亮显示要找的文本(一般会使用别名默认启用该功能)

grep - 例 1
grep - 例 1

grep - 例 2
grep - 例 2

cut - 截取每一行的特定文本

1
2
3
4
5
# 在以某字符为分隔的数据中,取出某段文本
cut -d '分隔字符' -f 要取出的段的序号 文件

# 以字符为单位取出文本
cut -c 字符序号区间 文件

序号从 1 开始。

ding@ding-server:~$ echo $PATH
/home/foxconn/.local/bin:/home/foxconn/.local/bin:/usr/lib/jvm/java-11-openjdk-amd64//bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games:/usr/local/games:/snap/bin

ding@ding-server:~$ echo $PATH | cut -d ':' -f 2,4,5
/home/foxconn/.local/bin:/usr/local/sbin:/usr/local/bin

ding@ding-server:~$ echo $PATH | cut -d ':' -f 2
/home/foxconn/.local/bin

ding@ding-server:~$ echo $PATH | cut -d ':' -f 2-4
/home/foxconn/.local/bin:/usr/lib/jvm/java-11-openjdk-amd64//bin:/usr/local/sbin

ding@ding-server:~$ echo $PATH | cut -d ':' -f 5-
/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games:/usr/local/games:/snap/bin

ding@ding-server:~$ echo $PATH | cut -d ':' -f -3
/home/foxconn/.local/bin:/home/foxconn/.local/bin:/usr/lib/jvm/java-11-openjdk-amd64//bin

ding@ding-server:~$ cat /etc/passwd
root:x:0:0:root:/root:/bin/bash
daemon:x:1:1:daemon:/usr/sbin:/usr/sbin/nologin
bin:x:2:2:bin:/bin:/usr/sbin/nologin
...
ding@ding-server:~$ cut -d ':' -f 1,6 /etc/passwd
root:/root
daemon:/usr/sbin
bin:/bin
...
ding@ding-server:~$ export
declare -x CLASSPATH=".:/usr/lib/jvm/java-11-openjdk-amd64//lib/dt.jar:/usr/lib/jvm/java-11-openjdk-amd64//lib/tools.jar"
declare -x DBUS_SESSION_BUS_ADDRESS="unix:path=/run/user/1000/bus"
...
ding@ding-server:~$ export | cut -c 12-
CLASSPATH=".:/usr/lib/jvm/java-11-openjdk-amd64//lib/dt.jar:/usr/lib/jvm/java-11-openjdk-amd64//lib/tools.jar"
DBUS_SESSION_BUS_ADDRESS="unix:path=/run/user/1000/bus"
...

限制

如果列之间用了多个空格,则不能使用该命令去提取。

ding@ding-server:~$ last
foxconn  pts/3        10.94.5.157      Tue May 17 14:11   still logged in
foxconn  pts/2        10.94.5.157      Tue May 17 10:42   still logged in
foxconn  pts/4        10.94.0.112      Tue May 17 09:07 - 10:10  (01:03)
...
ding@ding-server:~$ last | cut -d ' ' -f 1
foxconn
foxconn
foxconn
foxconn
ding@ding-server:~$ last | cut -d ' ' -f 2


...

sort - 排序

sort [选项] 文件

选项:

  • -f:忽略大小写
  • -b:忽略最前面的空格
  • -M:以月份名称(JANDEC...)排序
  • -n:使用纯数字排序(默认是以文字形式排序的)
  • -r:反向排序
  • -u:去重
  • -t 分隔符号:指定分隔符号(默认是 Tab
  • -k 段序号或区间:以哪个 / 哪些段排序(用法和 cut 一样)
  • -c:检查文件是否有序

例:

ding@ding-server:~$ sort /etc/passwd
admin:x:1002:1002:admin,,,:/home/admin:/bin/bash
_apt:x:105:65534::/nonexistent:/usr/sbin/nologin
backup:x:34:34:backup:/var/backups:/usr/sbin/nologin
...
ding@ding-server:~$ sort -t ':' -k 3 /etc/passwd
root:x:0:0:root:/root:/bin/bash
foxconn:x:1000:1000:foxconn:/home/foxconn:/bin/bash
systemd-network:x:100:102:systemd Network Management,,,:/run/systemd:/usr/sbin/nologin
J6100354:x:1001:1001::/home/J6100354:/bin/sh
...
ding@ding-server:~$ sort -t ':' -k 3 -n /etc/passwd
root:x:0:0:root:/root:/bin/bash
daemon:x:1:1:daemon:/usr/sbin:/usr/sbin/nologin
bin:x:2:2:bin:/bin:/usr/sbin/nologin
sys:x:3:3:sys:/dev:/usr/sbin/nologin
...
ding@ding-server:~$ sort -t ':' -k 3 -n -r /etc/passwd
nobody:x:65534:65534:nobody:/nonexistent:/usr/sbin/nologin
admin:x:1002:1002:admin,,,:/home/admin:/bin/bash
...

uniq - 去重

uniq [选项] 文件

只会在连续的范围内去重。

选项:

  • -i:忽略大小写
  • -c:计数
  • -u:仅输出无重复的行

例:

ding@ding-server:~$ last | cut -d ' ' -f 1 | uniq
foxconn
reboot
foxconn
reboot
foxconn
...
ding@ding-server:~$ last | cut -d ' ' -f 1 | sort | uniq

foxconn
reboot
wtmp
ding@ding-server:~$ last | cut -d ' ' -f 1 | sort | uniq -c
      1 
    198 foxconn
     12 reboot
      1 wtmp

wc - 字数统计

wc [选项] 文件或目录

如果有多个文件,会在最下方列出总和。

选项:

  • 无选项:列出行数、词数、字节数和文件名(如有)
  • -l:多少行
  • -w:多少词(以英文的方式判定)
  • -c:多少字节
  • -m:多少字符(换行符也包含在内)
  • -L:列出最长一行的字符长度

例:

ding@ding-server:~$ cat wctest.txt
This is some text.
这是一段文本示例。
123456
ding@ding-server:~$ wc wctest.txt
 3  6 54 wctest.txt
ding@ding-server:~$ wc -m wctest.txt
36 wctest.txt

ding@ding-server:~$ last | cut -d ' ' -f 1 | grep [a-zA-Z] | grep -v 'wtmp' | grep -v 'reboot' | grep -v 'unknown' | sort | uniq | wc -l    # 显示登录过的用户数量
2

tee - 双向重定向

tee [选项] 文件

tee 示意图
tee 示意图

选项: - -a:追加

常用在两个命令之间:

... | tee [选项] 文件 | ...

tee 示意图 - 管道
tee 示意图 - 管道

例:

ding@ding-server:~$ last | tee last.txt | cut -d ' ' -f 1 | sort | uniq

foxconn
reboot
wtmp
ding@ding-server:~$ cat last.txt
foxconn  pts/3        10.94.5.157      Tue May 17 14:11   still logged in
foxconn  pts/2        10.94.5.157      Tue May 17 10:42   still logged in
foxconn  pts/4        10.94.0.112      Tue May 17 09:07 - 10:10  (01:03)
...

历史记录

查看和执行历史命令

执行 history 可以查看最近的命令。如果限定查看最近的若干条命令,可以加上数字作为参数。

Bash 记录历史命令时,会把命令里面的字符记录进去,所以一般不要在命令中写密码等敏感信息

1
2
3
4
5
6
ding@ding-server:~$ history
 1216  sudo docker-compose -f docker-compose-mysql.yml down
...
 2213  sudo whoami
 2214  whoami
 2215  history

执行历史命令时,使用 ! 加上一些参数,例子见下。

执行时,首先会列出将要执行的命令。

ding@ding-server:~$ !2214   # 执行历史记录中的第 2214 条命令
whoami
ding
ding@ding-server:~$ !sudo   # 执行开头为 sudo 的最近的命令
sudo whoami
root
ding@ding-server:~$ !2214
whoami
ding
ding@ding-server:~$ !!  # 执行最近一条命令
whoami
ding
ding@ding-server:~$ sudo !!
sudo whoami
root

历史记录的操作

1
2
3
4
5
6
7
8
# 立即将目前的数据写入历史记录文件(默认为 ~/.bash_history)
history -w

# 查看历史记录保留的最大条数
echo ${HISTSIZE}

# 清除当前用户的历史记录
history -c

Shell 相关

Shell 的概念与作用

Shell(壳层、壳程序) 是为用户提供用户界面的软件。能够操作应用程序。相对于内核(Kernel)而言。

有命令行和图形界面之分:

  • 命令行(狭义的 Shell):bash、cmd、Powershell…
  • 图形界面:Gnome、KDE、Windows Explorer…

Shell 在计算机体系中的位置
Shell 在计算机体系中的位置

查看 Shell 相关信息

以下提供了两个系统中两种 Shell 的操作,操作分别为:

  1. 查看当前 Shell
  2. 查看可用的 Login Shell (CentOS 中也可以使用 chsh -l 查看)
  3. 查看 sh 被指定为哪个 Shell
# CentOS Linux release 7.9.2009,  fish
# 查看当前 Shell
ding@ding-server ~> echo $SHELL
/usr/bin/fish
# 查看可用的 Login Shell
ding@ding-server ~> cat /etc/shells
/bin/sh
/bin/bash
/sbin/nologin
/usr/bin/sh
/usr/bin/bash
/usr/sbin/nologin
/bin/tcsh
/bin/csh
/bin/zsh
/usr/bin/fish
# 查看 sh 被指定为哪个 Shell
ding@ding-server ~> ll /bin/sh
lrwxrwxrwx. 1 root root 4 Jul 15  2021 /bin/sh -> bash*
# Ubuntu 20.04 LTS, bash
# 查看当前 Shell
ding@ding-server:~$ echo $SHELL
/bin/bash
# 查看可用的 Login Shell
ding@ding-server:~$ cat /etc/shells
# /etc/shells: valid login shells
/bin/sh
/bin/bash
/usr/bin/bash
/bin/rbash
/usr/bin/rbash
/bin/dash
/usr/bin/dash
/usr/bin/tmux
/usr/bin/screen
# 查看 sh 被指定为哪个 Shell
ding@ding-server:~$ ll /bin/sh
lrwxrwxrwx 1 root root 4 Feb 23 16:50 /bin/sh -> dash*

sh、bash、dash

通常的 Shell 脚本,默认会以 sh 执行。

传统意义上的 sh 指 Bourne Shell,由 AT&T 公司的 Steve Bourne 开发,是 UNIX 系统上的标准 Shell。

Linux 上一般会指向 bash(Bourne Again Shell),属于 GNU 计划的一部分。

但 Debian 发行版中指向的是 dash(Debian Almquist Shell),由 ash(Almquist Shell ,BSD 等上的预装 Shell) 发展而来,更为小巧。

dash 对一些命令的支持与 bash 不同(如 echo 命令不支持 -e 选项),故写、执行脚本时要注意。

部分 Borne Shell 兼容 Shell 的发展历程(箭头只代表派生关系)
部分 Borne Shell 兼容 Shell 的发展历程(箭头只代表派生关系)

内部(built-in)命令与外部命令

内部命令指 Shell 程序自带的命令;外部命令指其他程序提供的命令。

内部命令的列表可以通过 enable 查看。

判断命令属于哪种,可以用 type [-a] 命令

内部命令与外部命令名称相同时,优先执行内部命令(如 pwd)。

各 Shell“对命令的支持不同”,一般指的是内部命令。

fish、zsh

  • Friendly Interactive Shell
  • Z Shell

有一些优化(如自动补全、高亮),适合用于个人工作。可以参考:

通过第三方插件,能够实现更好的效果。

但配置文件、脚本语法等和 bash 有不可忽视的差别,尤其是配置文件。

macOS 的终端默认使用 zsh。

fish 一例
fish 一例

zsh 安装 Oh My Zsh 插件后的效果一例
zsh 安装 Oh My Zsh 插件后的效果一例

csh、tcsh

Linux 中如果有 csh( C shell ),一般指向 tcsh(Tenex C shell),有补全等功能。

bash 的一些功能(如历史记录、别名)受其影响。

与 bash 的语法和配置文件差别很大。

现在很少用。

tcsh 一例
tcsh 一例

nologin

打印账号不可用的信息(可通过 /etc/nologin.txt 配置),返回非零值。

用于不可登录的用户:给其配置默认 Shell 为它,使其不可通过 Shell 登录。

ding@ding-server:~$ /sbin/nologin
This account is currently not available.
ding@ding-server:~$ echo $?
1
ding@ding-server:~$ cat /etc/passwd
...
games:x:5:60:games:/usr/games:/usr/sbin/nologin
...
ding@ding-server:~$ sudo su - games
This account is currently not available.
ding@ding-server:~$

rbash

是受限制的 bash,严格意义上来说是 bash 的特殊功能,相当于 bash -r

在 Debian 系发行版中,rbash 指向 bash(但实际效果不一样);如果没有,可以创建符号链接。

可以用作中转服务器,或者仅使用 ssh 来访问网页等等。

受限的情况:https://www.howtoing.com/rbash-a-restricted-bash-shell-explained-with-practical-examples/

但不绝对安全,可以通过运行 bash 等方式脱离控制。

screen(一种终端复用工具)

创建会话:

screen
screen 命令

离开会话:按 Ctrl + A,再按 D

查看会话:

1
2
3
4
ding@ding-server:~$ screen -ls
There is a screen on:
        1524444.pts-1.hcm0153   (05/19/2022 03:02:27 PM)        (Detached)
1 Socket in /run/screen/S-foxconn.

重进上面的会话:

screen -r 152444

tmux(另一种终端复用工具)

  • 默认前缀键:Ctrl + B
  • 左右分屏:前缀 + %
  • 上下分屏:前缀 + "
  • 切换:前缀 + 方向键
  • 调整:前缀 + + + 方向键
  • 关闭:前缀 + x ...

tmux 一例
tmux 一例

部分 Shell 和其他工具的关系

部分 Shell 和其他工具的关系
部分 Shell 和其他工具的关系

参考资料