Bash 语法 #
下标数组 #
bash
# 定义数组(空数组)
aa=()
# 或者:
declare -a aa
# 定义数组(赋初值)
aa=(a b c)
# 或者:
declare -a aa=([0]="a" [1]="b" [2]="c")
# 获取下标为0的元素(下标从0开始)
echo "${aa[0]}"
# 获取数组长度
echo "${#aa[@]}"
# 修改元素
aa[0]=x
# 添加3个元素:"d", "e", "f g"
aa+=(d e "f g")
# 保留下标1开始的所有元素(双引号不能省略)
aa=("${aa[@]:1}")
# 例:
aa=(a b c)
echo "${aa[@]:1}" # 输出:b c
aa=("${aa[@]:1}")
echo "${aa[@]}" # 输出:b c
declare -p aa # 输出:declare -a aa=([0]="b" [1]="c")
# 保留下标4开始的2个元素(双引号不能省略)
aa=("${aa[@]:4:2}")
# 删除下标4到6的元素(相当于保留下标0开始的4个元素以及下标7开始的所有元素)
aa=("${aa[@]:0:4}" "${aa[@]:7}")
# 删除下标为1的元素
unset 'aa[1]'
# 例:
aa=(a b c)
unset 'aa[1]'
echo "${aa[@]}" # 输出:a c
declare -p aa # 输出:declare -a aa=([0]="a" [2]="c")
关联数组 #
bash
# 定义一个关联数组名为 obj
declare -A obj
# 添加 key(字符串),值为 value(字符串)
obj[key]=value
输入输出重定向 #
bash
# 将命令的错误输出、标准输出分别重定向到 err、out 文件。
COMMAND 2> ./err > ./out
COMMAND 2>> ./err >> ./out # 追加写入
# 将当前 Shell 进程的错误输出、标准输出分别重定向到 err、out 文件。
exec 2> ./err > ./out
# 将命令的错误输出重定向到文件描述符1(即标准输出)。
COMMAND 2>&1
# 将命令的标准输出重定向到文件描述符2(即错误输出)。
COMMAND >&2
# 将命令的标准输出重定向到文件描述符3。
COMMAND >&3
# 将命令的错误输出、标准输出都重定向到 out 文件。
COMMAND &> ./out # 相当于 COMMAND >./out 2>&1 。若将 2>&1 放在 >./out 前面,则错误输出被重定向到标准输出而不是最终的 out 文件。
COMMAND &>> ./out # 追加写入
COMMAND &> /dev/null # 丢弃错误输出和标准输出
exec &> /dev/null # 将当前 Shell 进程的错误输出、标准输出重定向到 /dev/null,即接下来的命令输出都将被丢弃。
# 将命令的标准输入重定向到 in 文件。
COMMAND < ./in
# 将代码块的标准输入、标准输出分别重定向到 in、out 文件。
# (in 文件只会被打开一次,而不是每执行一条命令都去打开文件从头读取,out 文件同理)
# (exec <./in >./out 作用域为当前 Shell 进程)
{ COMMANDS; } < ./in > ./out
# 循环读取 in 文件,每次读取一行,读完为止。
while IFS= read -r line; do echo "$line"; done < ./in
命令替换(Command Substitution) #
语法格式:
bash
$(COMMANDS)
# 或者
`COMMANDS`
工作原理:
- 先在子 Shell 进程中执行命令 COMMANDS。
- 然后将其标准输出内容粘贴回命令行作为主命令的参数。
替换特点:
- 所有尾随换行符会被删除。
- 嵌入的换行符不会被删除(但在分词过程中可以被删除,因此是否被双引号包围至关重要)。
$(<FILE)
效率高于$(cat FILE)
,因为前者不需要创建子进程去运行 cat 命令。
示例:
bash
# 将 ./app.pid 文件中的内容当做 kill 命令的参数。
kill "$(cat ./app.pid)"
# 循环10次。
# 注意:这里的 seq 子命令输出“1”到“10”这些数字(数字之间由一个空格分隔),
# 而 for in 语句需要这十个参数而不是一整串结果作为一个参数,因此命令替换不能用双引号包围。
for i in $(seq 1 10); do echo "$i"; done
# 这里的尾随换行符被删除,但内嵌换行符被保留。
echo -en "$(echo -en '1\n\n2\n\n')" | hexdump -C
# Output:
# 00000000 31 0a 0a 32 |1..2|
# 00000004
# 这里的尾随换行符被删除,同时内嵌空白字符由于分词导致被删除。
# 另外,echo 命令在输出第二个参数之前会先输出一个空格,因此最终输出“1空格2”。
echo -en $(echo -en '1\n\n2\n\n') | hexdump -C
# Output:
# 00000000 31 20 32 |1 2|
# 00000003
进程替换 (Process Substitution) #
语法格式:
- 输出形式:
<(COMMANDS)
- 输入形式:
>(COMMANDS)
工作原理:
- 创建一个特殊的 FIFO(命名管道)或使用
/dev/fd
中的文件描述符。 - 在后台启动括号内的命令。
- 将命令的输入/输出连接到这个特殊文件。
- 将这个文件名作为参数传递给主命令。
示例:
bash
cat <(echo 123)
cat <(cat <(echo 123)) # 可以无限套娃
# Output: 123
# 这里的 echo 命令并没有读取临时pipe文件内容,只是将文件路径打印出来。
echo <(cat ./file)
# Output: /dev/fd/63
# 判断两个目录下文件列表的差异
diff <(ls dir1) <(ls dir2)
# 判断ssh私钥文件和ssh公钥文件是否一对
diff <(ssh-keygen -ef ~/.ssh/id_rsa) <(ssh-keygen -ef ~/.ssh/id_rsa.pub)
# 将子命令的标准输出传递给当前命令的标准输入。
COMMAND < <(SUB_COMMAND)
# 相当于管道命令: SUB_COMMAND | COMMAND ,
# 但是需要注意,如果 COMMAND 会改变当前 Shell 环境(比如 read 命令会向当前 Shell 进程添加/修改变量),
# 则不建议使用管道命令形式,因为管道命令是在子 Shell 中执行的,无法改变当前 Shell 环境。
此处字符串(Here String) #
bash
COMMAND <<< "string"
作用:
- 将一个字符串直接传递给一个命令的标准输入。
特点:
- 效率高于管道(如
echo "string" | COMMAND
),因为 Here String 直接在当前 Shell 中处理。 - 自动在末尾添加一个换行符,行为与 echo 一致。若不想要末尾换行符,则建议这种
COMMAND < <(echo -n "string")
。
此处文档(Here Document) #
语法格式:
bash
COMMAND << delimiter
Hi
This is a document
delimiter
工作原理:
<<
是 Here Document 的起始标记。delimiter
是一个自定义的结束标记(常用EOF
或END
)。- 从下一行开始的所有内容都会被作为输入,直到遇到单独的一行
delimiter
。 - 默认支持变量替换和命令替换。
变体形式:
bash
# 忽略前导制表符(Tab)
COMMAND <<- delimiter
Hi
This is a document
delimiter
# 禁用变量替换和命令替换(须用引号包围结束标记)
COMMAND << 'delimiter'
$PWD is not replaced
delimiter
判断语句 #
bash
# 判断是否文件存在(与 `[` 或 `test` 一致)
[[ -f file ]]
# 判断数字大小(只支持整数)(与 `[` 或 `test` 一致)
[[ 2 -gt 1 ]]
# 判断字符串是否相等
[[ str1 = str2 ]]
[[ str1 == str2 ]]
[[ str1 != str2 ]]
# 正则判断(注意,正则特殊字符被反斜杠转义或被引号引起来的话会变成普通字符)
[[ content =~ ^[0-9]+$ ]]
[[ content =~ '$'* ]]
[[ content =~ ^\ +$ ]]
# 与、或
[[ 2 -gt 1 && 2 -lt 3 ]]
[[ 2 -gt 1 || 2 -lt 3 ]]
read 和 readarray #
read #
text
Read a line from the standard input and split it into fields.
read [-ers] [-a array] [-d delim] [-i text] [-n nchars] [-N nchars] [-p prompt] [-t timeout] [-u fd] [name ...]
-a array 将内容分割后保存到名为 array 的数组变量中(下标从 0 开始)(单词分隔符请查阅 IFS 变量,与下面的 -d 选项无关)
-d delim 指定一个行分隔字符(默认是换行符),读取到这个分隔符后退出,分隔符不会被保存到变量中
-n nchars 读取 nchars 个字符,而不是一行(遵守分隔符)
-p prompt 读取内容之前先输出提示语
-r 不转义反斜杠字符
-s 不回显读取内容(仅当使用终端进行输入时)
-t timeout 读取超时秒数
-u fd 指定文件描述符进行读取(默认是标准输入)
Note
若指定了多个变量名称,则这些变量会按顺序接收输入的单词,最后一个变量接收剩余所有内容,单词分隔符由环境变量 IFS
指定。
此外还需要注意,即使只指定了一个变量名称,也会按照单词分割,导致前后空白字符丢失,若要保留空白字符请将 IFS
环境变量设置为空字符串。
示例:
bash
# 将输入的两个单词分配给两个变量(每个单词前后空白字符都会被移除)
read -r arg1 arg2 <<< " apple banana "
# arg1="apple"
# arg2="banana"
# 输入密码(不回显),将内容保存到 password 变量中
IFS= read -rsp "Enter password: " password
# 每次读取一行进行处理(注意,空行也会被读取变成一个空字符串)
while IFS= read -r line; do
echo "$line"
done < ./file
readarray #
readarray 是 mapfile 指令的别名。
text
Read lines from the standard input into an indexed array variable.
readarray: readarray [-d delim] [-n count] [-O origin] [-s count] [-t] [-u fd] [-C callback] [-c quantum] [array]
-d delim 指定一个分隔字符(默认是换行符)
-t 去掉末尾的分隔符(非分隔符都会被当做正常字符被包含)
-O index 指定起始索引(默认是0)
-s count 跳过前 count 个元素
-n count 读取总数
-C callback 每读取指定个数的元素后调用回调函数
-c quantum 与 -C 选项配合使用,读取元素个数
Note
readarray 命令不会像 read 命令那样移除开头和末尾的空白字符,默认全部保留(包括分隔符),可以添加 -t
选项来去除分隔符。
示例:
bash
# 读取文件,将每一行内容都保存到 lines 数组中
readarray -t lines < ./file
# 每次读取两行(注意,空行也会被读取变成一个空字符串)
while readarray -t -n 2 arr && [ "${#arr[@]}" -gt 0 ]; do
echo "${arr[0]}: ${arr[1]}"
done < ./file
# 将 dir 目录下的所有文件路径、目录路径保存到 names 数组中
readarray -t -d '' names < <(find ./dir -print0)
# 同样的:
readarray -t names < <(find ./dir)
# 避免这种写法:(因为输入末尾会多一个换行符,会导致数组多出一个空字符串数据)
readarray -t names <<< "$(find ./dir)"