# 简介
终端(Terminal)是一个命令行界面,只是一个空壳,用于传递输入到内部的命令解释器(Shell)中,一般来说 Shell 有
- Bash
- CMD
Bash 是一类 Linux 上常用的 Shell,主要目的是编写 Linux 的命令脚本。本文默认已经具备足够的 Linux 基础知识,只讲解 Bash 的基本事项
在 Linux 与 MacOS 中,打开终端即可使用 Bash
在 Windows 中,可以利用 WSL 进入 Linux 环境,从而使用 Bash
一般来说,例如 Kali 或者 MacOS 等系统,在进入终端后默认是 Z Shell (Zsh),而不是 Bash
相较于 Bash
- Zsh 具有自动补全
- Zsh 具有拼写纠错
- Zsh 速度较慢
在终端输入 exec bash 可以回退到 Bash 环境(建议回退)
通过命令确认 Bash 可用性
bash --version |
通过命令确认当前会话的环境变量
env |
可以通过该指令单独查看环境变量,例如
- 查看默认的 Shell 路径:
echo ${SHELL} - 查看当前 Bash 版本:
echo ${BASH_VERSION} - 查看当前 Bash 进程 ID:
echo ${BASHPID} - 查看当前用户所属组:
echo ${GROUPS} - 查看主机名:
echo ${HOSTNAME} - 查看系统类型:
echo ${OSTYPE} - 查看当前所处目录:
echo ${PWD} - 生成一个 0 到 32767 之间的随机数:
echo ${RANDOM} - 查看当前用户的 ID:
echo ${UID}
# Bash 脚本
Bash 命令除了直接在终端输入执行,也可以通过编辑器提前编写成脚本,并保存为 .sh 文件
主流编辑器,例如 VSCode 可以自动高亮 Bash 代码
所有的脚本在第一行都固定需要显式写出 Shell 路径,对于一般的 Bash 来说,应该是以下几行之一
#!/bin/bash#!/usr/bin/env bash
在指定路径的同时,也可以给 Bash 添加参数,例如
-x:在执行脚本时显示每条命令的执行过程,常用于 Debug-n:检查脚本的语法而不执行,常用于 Debug-r:安全模式下运行
总的来说,编写一个 shell.sh 文件,并且在开头写上
#!/bin/bash -r |
等价于运行时包含参数
bash -r shell.sh |
但是,编写完一个 Bash 脚本后,需要赋予执行权限
chmod u+x shell.sh |
执行当前目录下的脚本
./shell.sh |
通过在脚本正文中使用 set 命令,也可以实现参数的开关,例如在下例中, set -x 开启 Debug 模式, set +x 关闭 Debug 模式
#!/bin/bash | |
echo "This is a normal script" | |
set -x | |
echo "This is a debug script" | |
set +x | |
echo "This is a normal script again" |
Bash 的注释方式是 # ,例如
# This is a comment |
Bash 中输出指令是 echo ,例如输出文字:
echo "Hello World" | |
# output: Hello World |
在 Bash 的逻辑中,只有结束当前的命令,才会开始下一段命令
至于各个命令是否正确执行,是没有直接确认的。编写 Bash 脚本时,需要考虑这一点
# 变量
Bash 中引用变量的方法是 ${VAR_NAME} ,在上文中已经给过了多个例子
需要注意的是,不同于其他语言,Bash 的变量是没有类型的,甚至没有字符串和数字的区分,所有的变量都是字符串
命名规则不再赘述,和其他语言类似
以下是一个变量赋值的例子
var1="Hello World" |
以下是一个引用变量的例子
echo "I want to say ${var1}" | |
# output: I want to say Hello World |
实际上变量不加花括号也可以运行,但是出于安全习惯考虑,建议加上
通过使用圆括号 () ,可以将命令的输出赋值给变量,例如
var2=$(ls) | |
echo "The current directory contains: ${var2}" | |
# output: The current directory contains: file1 file2 file3 |
通过 unset 命令,可以删除一个变量,例如
var1="Hello World" | |
unset var1 | |
echo "The value of var1 is: ${var1}" | |
# output: The value of var1 is: |
# 数组
Bash 中可以配置一维数组,并从序号 0 开始访问
赋值方法为 array_name=(value1 value2 value3) ,访问方法为 ${array_name[index]} ,如果序号为 * ,则代表访问整个数组,例如
my_array=(apple banana cherry) | |
echo ${my_array[0]} | |
# output: apple | |
echo ${my_array[1]} | |
# output: banana | |
echo ${my_array[*]} | |
# output: apple banana cherry |
unset 命令也可以用于删除数组元素,例如
my_array=(apple banana cherry) | |
unset my_array[1] | |
echo ${my_array[*]} | |
# output: apple cherry |
可以通过指定序号来直接替换数组元素,例如
my_array=(apple banana cherry) | |
my_array[1]="grape" | |
echo ${my_array[*]} | |
# output: apple grape cherry |
# 运算符
以下是 Bash 中常见的运算符示例
+:加法-:减法*:乘法/:除法%:取模+=:加法赋值-=:减法赋值
通过 let 命令,可以执行算术运算,例如
let result="4 * 5" | |
echo ${result} | |
# output: 20 |
Bash 不是没有变量类型吗,为什么可以计算?:
let会把给出的内容当作算术表达式解释,也就是说let result="4 * 5"会被解释为result=4 * 5,从而得到结果20
实际上更推荐以下方式:通过圆括号可以将内容视为表达式命令,例如 (4 * 5) ,直接在 Bash 中执行也可以得到结果
利用上文中提到的 $() 引用输出方法,可以将表达式赋值写为
result=$(echo $((4 * 5))) | |
echo ${result} | |
# output: 20 |
此外,运算也可以使用 expr ,比起命令,这更应该说是一个外部程序,也不常见
这个程序将后文的内容当作参数传递,并且给出合理的结果,例如
result=$(expr 4 \* 5) | |
echo ${result} | |
# output: 20 |
需要注意的是: * 在 Shell 中是一个特殊字符,代表通配符,所以需要使用 \ 进行转义
Bash 中主要有以下几种控制运算符
&: 将命令放在后台执行(不会等待运行结束)&&: 连接两个命令,只有前一个命令成功执行(返回值为 0)时才会执行后一个命令(): 将命令视为一个整体执行;: 将多个命令分开执行,前一个命令执行完后无论成功与否都会执行下一个命令;;: 用于终止case语句|: 将前一个命令的输出作为后一个命令的输入,称为管道||: 连接两个命令,只有前一个命令失败执行(返回值不为 0)时才会执行后一个命令
例如:先尝试创建 test 文件,成功了再创建 test123 文件
touch test && touch test123 |
将 ls 与 ps 视为一个整体执行,直接返回当前目录下的文件和当前运行的进程。如果不使用括号, ls 会先执行,返回当前目录下的文件,然后 ps 会执行,返回当前运行的进程。中间会被输入截断
(ls; ps) |
例如:在命令失败后回报错误信息(虚构的命令)
lzl || echo "Command failed" |
流是程序与运行环境之间的交互方式,Bash 中有三种流
- 标准输入(stdin):默认从键盘输入数据,文件描述符为 0
- 标准输出(stdout):默认将数据输出到屏幕,文件描述符为 1
- 标准错误(stderr):默认将错误信息输出到屏幕,文件描述符为 2
通过流,Bash 可以实现输入输出的重定向,使得命令的输入输出可以被重定向到文件或者其他命令中,此时常用下列重定向运算符:
>:将标准输出重定向到文件,覆盖原有内容>>:将标准输出重定向到文件,追加内容&>, >&:将标准输出和标准错误重定向到文件,覆盖原有内容.&>>:将标准输出和标准错误重定向到文件,追加内容<:将标准输入重定向到文件,从文件中读取数据<<:将标准输入重定向到一个字符串,直到遇到指定的结束标记,该功能被称为 Here Document(Heredoc)|:将一个命令的标准输出重定向到另一个命令的标准输入,称为管道
需要提醒的是,重定向运算符使得 Bash 将输出送到了文件而不是终端中,所以不会显示输出
例如:将 Hello World 存入 output.txt 文件中,再在尾部追加内容
echo "Hello World" > output.txt | |
cat output.txt | |
# output: Hello World | |
echo "Goodbye" >> output.txt | |
cat output.txt | |
# output: Hello World | |
# Goodbye |
例如:将所有的输出和错误信息都重定向到 log.txt 文件中,这位于日志记录中非常常见
ls -l / &> log.txt |
此外,通过编号可以实现将输出和错误信息分别重定向到不同的文件中,例如
ls -l / 1> output.txt 2> error.txt |
例如:将 output.txt 文件的内容作为标准输入,传递给 cat 命令(这里 cat 根本不知道文件名)
echo "Hello World" > output.txt | |
cat < output.txt | |
# output: Hello World |
利用分段符号 EOF ,可以将多行文本输入到命令中,例如
cat << EOF | |
This is a multi-line text input | |
And it will be passed to the cat command | |
EOF | |
# output: This is a multi-line text input | |
# And it will be passed to the cat command |
例如:将前者的输出传递给后者,实现管道
ls -l / | grep "bin" | |
# output: drwxr-xr-x 2 root root 4096 Jan 1 00:00 bin |
# 位置参数
用户在执行 Bash 脚本时,可以赋予其参数,例如
./script.sh arg1 arg2 arg3 |
这里 arg1 、 arg2 和 arg3 就是位置参数,在脚本中可以通过 $1 、 $2 和 $3 来访问这些参数,例如
#!/bin/bash | |
echo "The first argument is: ${1}" | |
echo "The second argument is: ${2}" | |
echo "The third argument is: ${3}" | |
# output: The first argument is: arg1 | |
# The second argument is: arg2 | |
# The third argument is: arg3 |
通过 $@ 可以访问所有的位置参数,通过 $# 可以访问位置参数的数量,例如
#!/bin/bash | |
echo "All arguments: ${@}" | |
echo "Number of arguments: ${#}" | |
# output: All arguments: arg1 arg2 arg3 | |
# Number of arguments: 3 |
此外,
$0代表脚本的名称$*代表将所有的位置参数作为一个字符串访问$?代表上一个命令的退出状态码
# 输入提示符
输入提示符指的是在脚本执行过程中要求用户输入数据的情况,可以通过 read 命令来实现
例如:提示用户输入姓名,并将其存储在变量 name 中
echo "Please enter your name:" | |
read name | |
echo "Hello, ${name}!" | |
# output: Please enter your name: | |
# Hello, [user input]! |
如果希望输入时不要将 \ 进行转义,可以使用 -r 参数,例如
# 退出状态码
Bash 的指令是否执行成功,可以由退出状态码来判断,退出状态码是一个 0~255 之间的整数
- 0:表示命令执行成功
- 非 0:表示命令执行失败,具体的非 0 数值可以代表不同的错误类型,具体数值的含义可以通过查阅相关文档来了解,例如
- 126:命令无法执行,可能是因为权限不足或者文件不可执行
- 127:命令未找到,可能是因为命令不存在或者路径错误
可以通过这样的例子来获取退出状态码
#!/bin/bash | |
ls -l > /dev/null | |
echo "The exit status of the last command is: $?" | |
# output: The exit status of the last command is: 0 |
慎重使用路径
/dev/null,它是一个特殊的设备文件,代表一个黑洞,任何写入它的数据都会被丢弃,而任何从它读取的数据都会得到 EOF(End of File)。因此,在上面的例子中,ls -l > /dev/null的作用是将ls -l命令的输出重定向到/dev/null,从而丢弃输出,只关注命令的执行结果。
活用退出状态码,可以在执行诸多命令前进行检查,例如下载前检查磁盘空间
在脚本中,可以通过 exit 命令来指定退出状态码,例如
#!/bin/bash | |
echo "This script will exit with status code 1" | |
exit 1 |
退出状态码不止是脚本,也是指令的,例如
ps -ef | |
echo $? | |
# output: 0 |