前言
bash
外部流程和程序的纯替代品集合。该bash
脚本语言是更强大的比人们认识和大多数任务能够不依赖于外部程序来完成。
调用外部进程bash
是昂贵的,过度使用将导致明显的减速。使用内置方法(如果适用)编写的脚本和程序将更快,需要更少的依赖性,并且可以更好地理解语言本身。
本书的内容为解决编写程序和脚本时遇到的问题提供了参考bash
。示例是函数格式,展示了如何将这些解决方案合并到代码中。
STRINGS
修剪字符串中的前导和尾随空格
这是一个替代品sed
,awk
,perl
和其他工具。
下面的函数通过查找所有前导和尾随空格并
从字符串的开头和结尾删除它来工作。使用:
内置代替临时变量。
示例功能:
trim_string() {
# Usage: trim_string " example string "
: "${1#"${1%%[![:space:]]*}"}"
: "${_%"${_##*[![:space:]]}"}"
printf '%s\n' "$_"
}
用法示例:
$ trim_string " Hello, World "
Hello, World
$ name=" John Black "
$ trim_string "$name"
John Black
修剪字符串中的所有空格并截断空格
这是一个替代品sed
,awk
,perl
和其他工具。
下面的函数通过滥用单词拆分来创建一个
没有前导/尾随空格和截断空格的新字符串。
示例功能:
# shellcheck disable=SC2086,SC2048
trim_all() {
# Usage: trim_all " example string "
set -f
set -- $*
printf '%s\n' "$*"
set +f
}
用法示例:
$ trim_all " Hello, World "
Hello, World
$ name=" John Black is my name. "
$ trim_all "$name"
John Black is my name.
在字符串上使用正则表达式
的结果bash
的正则表达式匹配可以用于代替sed
用于
大量的用例。
CAVEAT:这是为数不多的平台相关bash
功能之一。
bash
将使用用户系统上安装的任何正则表达式引擎。
如果要兼容,请坚持使用POSIX正则表达式功能。
CAVEAT:此示例仅打印第一个匹配组。使用
多个捕获组时,需要进行一些修改。
示例功能:
regex() {
# Usage: regex "string" "regex"
[[ $1 =~ $2 ]] && printf '%s\n' "${BASH_REMATCH[1]}"
}
用法示例:
$ # Trim leading white-space.
$ regex ' hello' '^\s*(.*)'
hello
$ # Validate a hex color.
$ regex "#FFFFFF" '^(#?([a-fA-F0-9]{6}|[a-fA-F0-9]{3}))$'
#FFFFFF
$ # Validate a hex color (invalid).
$ regex "red" '^(#?([a-fA-F0-9]{6}|[a-fA-F0-9]{3}))$'
# no output (invalid)
脚本中的示例用法:
is_hex_color() {
if [[ $1 =~ ^(#?([a-fA-F0-9]{6}|[a-fA-F0-9]{3}))$ ]]; then
printf '%s\n' "${BASH_REMATCH[1]}"
else
printf '%s\n' "error: $1 is an invalid color."
return 1
fi
}
read -r color
is_hex_color "$color" || color="#FFFFFF"
# Do stuff.
在分隔符上拆分字符串
这是一个另类cut
,awk
和其他工具。
示例功能:
split() {
# Usage: split "string" "delimiter"
IFS=$'\n' read -d "" -ra arr <<< "${1//$2/$'\n'}"
printf '%s\n' "${arr[@]}"
}
用法示例:
$ split "apples,oranges,pears,grapes" ","
apples
oranges
pears
grapes
$ split "1, 2, 3, 4, 5" ", "
1
2
3
4
5
# Multi char delimiters work too!
$ split "hello---world---my---name---is---john" "---"
hello
world
my
name
is
john
将字符串更改为小写
CAVEAT:需要bash
4+
示例功能:
lower() {
# Usage: lower "string"
printf '%s\n' "${1,,}"
}
用法示例:
$ lower "HELLO"
hello
$ lower "HeLlO"
hello
$ lower "hello"
hello
将字符串更改为大写
CAVEAT:需要bash
4+
示例功能:
upper() {
# Usage: upper "string"
printf '%s\n' "${1^^}"
}
用法示例:
$ upper "hello"
HELLO
$ upper "HeLlO"
HELLO
$ upper "HELLO"
HELLO
修剪字符串中的引号
示例功能:
trim_quotes() {
# Usage: trim_quotes "string"
: "${1//\'}"
printf '%s\n' "${_//\"}"
}
用法示例:
$ var="'Hello', \"World\""
$ trim_quotes "$var"
Hello, World
从字符串中删除所有模式实例
示例功能:
strip_all() {
# Usage: strip_all "string" "pattern"
printf '%s\n' "${1//$2}"
}
用法示例:
$ strip_all "The Quick Brown Fox" "[aeiou]"
Th Qck Brwn Fx
$ strip_all "The Quick Brown Fox" "[[:space:]]"
TheQuickBrownFox
$ strip_all "The Quick Brown Fox" "Quick "
The Brown Fox
从字符串中剥离第一次出现的模式
示例功能:
strip() {
# Usage: strip "string" "pattern"
printf '%s\n' "${1/$2}"
}
用法示例:
$ strip "The Quick Brown Fox" "[aeiou]"
Th Quick Brown Fox
$ strip "The Quick Brown Fox" "[[:space:]]"
TheQuick Brown Fox
从字符串开始剥离图案
示例功能:
lstrip() {
# Usage: lstrip "string" "pattern"
printf '%s\n' "${1##$2}"
}
用法示例:
$ lstrip "The Quick Brown Fox" "The "
Quick Brown Fox
从字符串末尾剥去图案
示例功能:
rstrip() {
# Usage: rstrip "string" "pattern"
printf '%s\n' "${1%%$2}"
}
用法示例:
$ rstrip "The Quick Brown Fox" " Fox"
The Quick Brown
百分比编码字符串
示例功能:
urlencode() {
# Usage: urlencode "string"
local LC_ALL=C
for (( i = 0; i < ${#1}; i++ )); do
: "${1:i:1}"
case "$_" in
[a-zA-Z0-9.~_-])
printf '%s' "$_"
;;
*)
printf '%%%02X' "'$_"
;;
esac
done
printf '\n'
}
用法示例:
$ urlencode "https://github.com/dylanaraps/pure-bash-bible"
https%3A%2F%2Fgithub.com%2Fdylanaraps%2Fpure-bash-bible
解码百分比编码的字符串
示例功能:
urldecode() {
# Usage: urldecode "string"
: "${1//+/ }"
printf '%b\n' "${_//%/\\x}"
}
用法示例:
$ urldecode "https%3A%2F%2Fgithub.com%2Fdylanaraps%2Fpure-bash-bible"
https://github.com/dylanaraps/pure-bash-bible
检查字符串是否包含子字符串
使用测试:
if [[ $var == *sub_string* ]]; then
printf '%s\n' "sub_string is in var."
fi
# Inverse (substring not in string).
if [[ $var != *sub_string* ]]; then
printf '%s\n' "sub_string is not in var."
fi
# This works for arrays too!
if [[ ${arr[*]} == *sub_string* ]]; then
printf '%s\n' "sub_string is in array."
fi
使用案例陈述:
case "$var" in
*sub_string*)
# Do stuff
;;
*sub_string2*)
# Do more stuff
;;
*)
# Else
;;
esac
检查字符串是否以子字符串开头
if [[ $var == sub_string* ]]; then
printf '%s\n' "var starts with sub_string."
fi
# Inverse (var does not start with sub_string).
if [[ $var != sub_string* ]]; then
printf '%s\n' "var does not start with sub_string."
fi
检查字符串是否以子字符串结尾
if [[ $var == *sub_string ]]; then
printf '%s\n' "var ends with sub_string."
fi
# Inverse (var does not end with sub_string).
if [[ $var != *sub_string ]]; then
printf '%s\n' "var does not end with sub_string."
fi
阵列
反转数组
启用extdebug
允许访问BASH_ARGV
数组,该数组
反向存储当前函数的参数。
示例功能:
reverse_array() {
# Usage: reverse_array "array"
shopt -s extdebug
f()(printf '%s\n' "${BASH_ARGV[@]}"); f "$@"
shopt -u extdebug
}
用法示例:
$ reverse_array 1 2 3 4 5
5
4
3
2
1
$ arr=(red blue green)
$ reverse_array "${arr[@]}"
green
blue
red
删除重复的数组元素
创建临时关联数组。设置关联数组
值并发生重复赋值时,bash会覆盖该键。这
允许我们有效地删除数组重复。
CAVEAT:需要bash
4+
示例功能:
remove_array_dups() {
# Usage: remove_array_dups "array"
declare -A tmp_array
for i in "$@"; do
[[ $i ]] && IFS=" " tmp_array["${i:- }"]=1
done
printf '%s\n' "${!tmp_array[@]}"
}
用法示例:
$ remove_array_dups 1 1 2 2 3 3 3 3 3 4 4 4 4 4 5 5 5 5 5 5
1
2
3
4
5
$ arr=(red red green blue blue)
$ remove_array_dups "${arr[@]}"
red
green
blue
随机数组元素
示例功能:
random_array_element() {
# Usage: random_array_element "array"
local arr=("$@")
printf '%s\n' "${arr[RANDOM % $#]}"
}
用法示例:
$ array=(red green blue yellow brown)
$ random_array_element "${array[@]}"
yellow
# Multiple arguments can also be passed.
$ random_array_element 1 2 3 4 5 6 7
3
循环一个数组
每次printf
调用时,都会打印下一个数组元素。当
打印到达最后一个数组元素时,它
再次从第一个元素开始。
arr=(a b c d)
cycle() {
printf '%s ' "${arr[${i:=0}]}"
((i=i>=${#arr[@]}-1?0:++i))
}
在两个值之间切换
这与上面的工作方式相同,这只是一个不同的用例。
arr=(true false)
cycle() {
printf '%s ' "${arr[${i:=0}]}"
((i=i>=${#arr[@]}-1?0:++i))
}
LOOPS
循环一系列数字
替代seq
。
# Loop from 0-100 (no variable support).
for i in {0..100}; do
printf '%s\n' "$i"
done
循环遍历可变数字范围
替代seq
。
# Loop from 0-VAR.
VAR=50
for ((i=0;i<=VAR;i++)); do
printf '%s\n' "$i"
done
循环数组
arr=(apples oranges tomatoes)
# Just elements.
for element in "${arr[@]}"; do
printf '%s\n' "$element"
done
循环遍历带索引的数组
arr=(apples oranges tomatoes)
# Elements and index.
for i in "${!arr[@]}"; do
printf '%s\n' "${arr[i]}"
done
# Alternative method.
for ((i=0;i<${#arr[@]};i++)); do
printf '%s\n' "${arr[i]}"
done
循环遍历文件的内容
while read -r line; do
printf '%s\n' "$line"
done < "file"
循环遍历文件和目录
不要用ls
。
# Greedy example.
for file in *; do
printf '%s\n' "$file"
done
# PNG files in dir.
for file in ~/Pictures/*.png; do
printf '%s\n' "$file"
done
# Iterate over directories.
for dir in ~/Downloads/*/; do
printf '%s\n' "$dir"
done
# Brace Expansion.
for file in /path/to/parentdir/{file1,file2,subdir/file3}; do
printf '%s\n' "$file"
done
# Iterate recursively.
shopt -s globstar
for file in ~/Pictures/**/*; do
printf '%s\n' "$file"
done
shopt -u globstar
文件处理
CAVEAT: bash
在版本中不能正确处理二进制数据< 4.4
。
将文件读取为字符串
替代cat
命令。
file_data="$(<"file")"
将文件读取到数组(按行)
替代cat
命令。
# Bash <4
IFS=$'\n' read -d "" -ra file_data < "file"
# Bash 4+
mapfile -t file_data < "file"
获取文件的前N行
替代head
命令。
CAVEAT:需要bash
4+
示例功能:
head() {
# Usage: head "n" "file"
mapfile -tn "$1" line < "$2"
printf '%s\n' "${line[@]}"
}
用法示例:
$ head 2 ~/.bashrc
# Prompt
PS1='➜ '
$ head 1 ~/.bashrc
# Prompt
获取文件的最后N行
替代tail
命令。
CAVEAT:需要bash
4+
示例功能:
tail() {
# Usage: tail "n" "file"
mapfile -tn 0 line < "$2"
printf '%s\n' "${line[@]: -$1}"
}
用法示例:
$ tail 2 ~/.bashrc
# Enable tmux.
# [[ -z "$TMUX" ]] && exec tmux
$ tail 1 ~/.bashrc
# [[ -z "$TMUX" ]] && exec tmux
获取文件中的行数
替代wc -l
。
示例函数(bash 4):
lines() {
# Usage: lines "file"
mapfile -tn 0 lines < "$1"
printf '%s\n' "${#lines[@]}"
}
示例函数(bash 3):
此方法使用的内存少于mapfile
方法,并在bash
3中工作,但对于较大的文件,它的速度较慢。
lines_loop() {
# Usage: lines_loop "file"
count=0
while IFS= read -r _; do
((count++))
done < "$1"
printf '%s\n' "$count"
}
用法示例:
$ lines ~/.bashrc
48
$ lines_loop ~/.bashrc
48
计算目录中的文件或目录
这是通过将glob的输出传递给函数然后计算参数的数量来实现的。
示例功能:
count() {
# Usage: count /path/to/dir/*
# count /path/to/dir/*/
printf '%s\n' "$#"
}
用法示例:
# Count all files in dir.
$ count ~/Downloads/*
232
# Count all dirs in dir.
$ count ~/Downloads/*/
45
# Count all jpg files in dir.
$ count ~/Pictures/*.jpg
64
创建一个空文件
替代touch
。
# Shortest.
>file
# Longer alternatives:
:>file
echo -n >file
printf '' >file
提取两个标记之间的线条
示例功能:
extract() {
# Usage: extract file "opening marker" "closing marker"
while IFS=$'\n' read -r line; do
[[ $extract && $line != "$3" ]] &&
printf '%s\n' "$line"
[[ $line == "$2" ]] && extract=1
[[ $line == "$3" ]] && extract=
done < "$1"
}
用法示例:
# Extract code blocks from MarkDown file.
$ extract ~/projects/pure-bash/README.md '```sh' '```'
# Output here...
文件路径
获取文件路径的目录名称
替代dirname
命令。
示例功能:
dirname() {
# Usage: dirname "path"
printf '%s\n' "${1%/*}/"
}
用法示例:
$ dirname ~/Pictures/Wallpapers/1.jpg
/home/black/Pictures/Wallpapers/
$ dirname ~/Pictures/Downloads/
/home/black/Pictures/
获取文件路径的基本名称
替代basename
命令。
示例功能:
basename() {
# Usage: basename "path"
: "${1%/}"
printf '%s\n' "${_##*/}"
}
用法示例:
$ basename ~/Pictures/Wallpapers/1.jpg
1.jpg
$ basename ~/Pictures/Downloads/
Downloads
变量
使用变量分配和访问变量
$ hello_world="value"
# Create the variable name.
$ var="world"
$ ref="hello_$var"
# Print the value of the variable name stored in 'hello_$var'.
$ printf '%s\n' "${!ref}"
value
或者,在bash
4.3+上:
$ hello_world="value"
$ var="world"
# Declare a nameref.
$ declare -n ref=hello_$var
$ printf '%s\n' "$ref"
value
根据另一个变量命名变量
$ var="world"
$ declare "hello_$var=value"
$ printf '%s\n' "$hello_world"
value
ESCAPE序列
与流行的看法相反,使用原始逃逸序列没有问题。使用tput
与手动打印相同的ANSI序列的摘要。更糟糕的是,tput
实际上并不便携。有许多tput
变体,每个变体都有不同的命令和语法(尝试tput setaf 3
使用FreeBSD系统)。原始序列很好。
文字颜色
注意:需要RGB值的序列仅适用于真彩色终端仿真器。
序列 | 它有什么作用? | 值 |
---|---|---|
\e[38;5;<NUM>m |
设置文本前景色。 | 0-255 |
\e[48;5;<NUM>m |
设置文本背景颜色。 | 0-255 |
\e[38;2;<R>;<G>;<B>m |
将文本前景色设置为RGB颜色。 |
R ,G ,B
|
\e[48;2;<R>;<G>;<B>m |
将文本背景颜色设置为RGB颜色。 |
R ,G ,B
|
文字属性
序列 | 它有什么作用? |
---|---|
\e[m |
重置文本格式和颜色。 |
\e[1m |
粗体。 |
\e[2m |
微弱的文字。 |
\e[3m |
斜体文字。 |
\e[4m |
下划线文字。 |
\e[5m |
慢慢眨眼。 |
\e[7m |
交换前景色和背景色。 |
光标移动
序列 | 它有什么作用? | 值 |
---|---|---|
\e[<LINE>;<COLUMN>H |
将光标移动到绝对位置。 |
line , column
|
\e[H |
将光标移动到原位(0,0 )。 |
|
\e[<NUM>A |
将光标向上移动N行。 | num |
\e[<NUM>B |
将光标向下移动N行。 | num |
\e[<NUM>C |
将光标向右移动N列。 | num |
\e[<NUM>D |
将光标向左移动N列。 | num |
\e[s |
保存光标位置。 | |
\e[u |
恢复光标位置。 |
删除文本
序列 | 它有什么作用? |
---|---|
\e[K |
从光标位置删除到行尾。 |
\e[1K |
从光标位置删除到行首。 |
\e[2K |
擦除整个当前行。 |
\e[J |
从当前行删除到屏幕底部。 |
\e[1J |
从当前行删除到屏幕顶部。 |
\e[2J |
清除屏幕。 |
\e[2J\e[H |
清除屏幕并将光标移动到0,0 。 |
参数扩展
间接
参数 | 它有什么作用? |
---|---|
${!VAR} |
根据值访问变量VAR 。 |
${!VAR*} |
展开为IFS 以VAR 。开头的变量名称的分隔列表。 |
${!VAR@} |
展开为IFS 以VAR 。开头的变量名称的分隔列表。如果是双引号,则每个变量名称都会扩展为单独的单词。 |
替换
参数 | 它有什么作用? |
---|---|
${VAR#PATTERN} |
从字符串的开头删除模式的最短匹配。 |
${VAR##PATTERN} |
从字符串的开头删除模式的最长匹配。 |
${VAR%PATTERN} |
从字符串末尾删除模式的最短匹配。 |
${VAR%%PATTERN} |
从字符串末尾删除模式的最长匹配。 |
${VAR/PATTERN/REPLACE} |
用字符串替换第一个匹配。 |
${VAR//PATTERN/REPLACE} |
用字符串替换所有匹配项。 |
${VAR/PATTERN} |
删除第一场比赛。 |
${VAR//PATTERN} |
删除所有比赛。 |
长度
参数 | 它有什么作用? |
---|---|
${#VAR} |
字符变量的长度。 |
${#ARR[@]} |
元素中的数组长度。 |
扩张
参数 | 它有什么作用? | |
---|---|---|
${VAR:OFFSET} |
N 从变量中删除第一个字符。 |
|
${VAR:OFFSET:LENGTH} |
从N 字符到N 字符获取子字符串。 |
|
(${VAR:10:10} :从char 10 到char 获取子字符串20 ) | ||
${VAR:: OFFSET} |
N 从变量中获取第一个字符。 |
|
${VAR:: -OFFSET} |
N 从变量中删除最后一个字符。 |
|
${VAR: -OFFSET} |
N 从变量中获取最后一个字符。 |
|
${VAR:OFFSET:-OFFSET} |
削减第一个N 字符和最后一个N 字符。 |
bash 4.2+ |
案例修改
参数 | 它有什么作用? | 警告 |
---|---|---|
${VAR^} |
大写第一个字符。 | bash 4+ |
${VAR^^} |
大写所有字符。 | bash 4+ |
${VAR,} |
小写第一个字符。 | bash 4+ |
${VAR,,} |
小写所有字符。 | bash 4+ |
默认值
参数 | 它有什么作用? |
---|---|
${VAR:-STRING} |
如果VAR 为空或未设置,请使用STRING 其值。 |
${VAR-STRING} |
如果VAR 未设置,请使用STRING 其值。 |
${VAR:=STRING} |
如果VAR 为空或未设置,请将值设置VAR 为STRING 。 |
${VAR=STRING} |
如果VAR 未设置,请将值设置VAR 为STRING 。 |
${VAR:+STRING} |
如果VAR 不为空,则使用STRING 其值。 |
${VAR+STRING} |
如果VAR 已设置,则使用STRING 其值。 |
${VAR:?STRING} |
如果为空或未设置,则显示错误。 |
${VAR?STRING} |
如果未设置则显示错误。 |
BRACE EXPANSION
范围
# Syntax: {<START>..<END>}
# Print numbers 1-100.
echo {1..100}
# Print range of floats.
echo 1.{1..9}
# Print chars a-z.
echo {a..z}
echo {A..Z}
# Nesting.
echo {A..Z}{0..9}
# Print zero-padded numbers.
# CAVEAT: bash 4+
echo {01..100}
# Change increment amount.
# Syntax: {<START>..<END>..<INCREMENT>}
# CAVEAT: bash 4+
echo {1..10..2} # Increment by 2.
字符串列表
echo {apples,oranges,pears,grapes}
# Example Usage:
# Remove dirs Movies, Music and ISOS from ~/Downloads/.
rm -rf ~/Downloads/{Movies,Music,ISOS}
有条件的表达
文件条件
表达 | 值 | 它有什么作用? |
---|---|---|
-a |
file |
如果文件存在。 |
-b |
file |
如果文件存在并且是块特殊文件。 |
-c |
file |
如果文件存在并且是字符特殊文件。 |
-d |
file |
如果文件存在且是目录。 |
-e |
file |
如果文件存在。 |
-f |
file |
如果文件存在且是常规文件。 |
-g |
file |
如果文件存在且其set-group-id位已设置。 |
-h |
file |
如果文件存在并且是符号链接。 |
-k |
file |
如果文件存在且其粘滞位已设置 |
-p |
file |
如果文件存在并且是命名管道(FIFO)。 |
-r |
file |
如果文件存在且可读。 |
-s |
file |
如果文件存在且其大小大于零。 |
-t |
fd |
如果文件描述符是打开的并且引用了终端。 |
-u |
file |
如果文件存在且其set-user-id位已设置。 |
-w |
file |
如果文件存在且可写。 |
-x |
file |
如果文件存在且可执行。 |
-G |
file |
如果文件存在且由有效组ID拥有。 |
-L |
file |
如果文件存在并且是符号链接。 |
-N |
file |
如果文件存在且自上次读取后已被修改。 |
-O |
file |
如果文件存在并且由有效用户ID拥有。 |
-S |
file |
如果文件存在且是套接字。 |
文件比较
表达 | 它有什么作用? |
---|---|
file -ef file2 |
如果两个文件都引用相同的inode和设备编号。 |
file -nt file2 |
如果file 比file2 (使用修改时间)更新或file 存在file2 而不存在。 |
file -ot file2 |
如果file 早于file2 (使用修改时间)或file2 存在file 而不存在。 |
可变条件
表达 | 值 | 它有什么作用? |
---|---|---|
-o |
opt |
如果启用了shell选项。 |
-v |
var |
如果变量具有指定的值。 |
-R |
var |
如果variable是名称引用。 |
-z |
var |
如果字符串的长度为零。 |
-n |
var |
如果字符串的长度不为零。 |
变量比较
表达 | 它有什么作用? |
---|---|
var = var2 |
等于。 |
var == var2 |
等于(同义词= )。 |
var != var2 |
不等于。 |
var < var2 |
小于(以ASCII字母顺序排列。) |
var > var2 |
大于(以ASCII字母顺序排列。) |
算术运算符
分配
运营商 | 它有什么作用? |
---|---|
= |
初始化或更改变量的值。 |
算术
运营商 | 它有什么作用? |
---|---|
+ |
加成 |
- |
减法 |
* |
乘法 |
/ |
师 |
** |
幂 |
% |
模 |
+= |
加 - 等于(增加变量。) |
-= |
减去等于(减少变量。) |
*= |
时间相等(乘以变量。) |
/= |
Slash-Equal(除以变量。) |
%= |
Mod-Equal(除去变量的剩余部分。) |
按位
运营商 | 它有什么作用? | |
---|---|---|
<< |
按位左移 | |
<<= |
左移平等 | |
>> |
按位右移 | |
>>= |
右移平等 | |
& |
按位AND | |
&= |
按位AND-Equal | |
` | ` | 按位OR |
` | =` | 按位OR-等于 |
~ |
按位NOT | |
^ |
按位异或 | |
^= |
按位XOR-Equal |
合乎逻辑
运营商 | 它有什么作用? | ||
---|---|---|---|
! |
不 | ||
&& |
和 | ||
` | ` | 要么 |
杂
运营商 | 它有什么作用? | 例 |
---|---|---|
, |
逗号分隔符 | ((a=1,b=2,c=3)) |
算术
设置变量的语法更简单
# Simple math
((var=1+2))
# Decrement/Increment variable
((var++))
((var--))
((var+=1))
((var-=1))
# Using variables
((var=var2*arr[2]))
三元测试
# Set the value of var to var2 if var2 is greater than var.
# var: variable to set.
# var2>var: Condition to test.
# ?var2: If the test succeeds.
# :var: If the test fails.
((var=var2>var?var2:var))
TRAPS
陷阱允许脚本在各种信号上执行代码。在pxltrm(用bash编写的像素艺术编辑器)中,陷阱用于在窗口大小调整时重绘用户界面。另一个用例是在脚本退出时清理临时文件。
应该在脚本开头附近添加陷阱,以便捕获任何早期错误。
注意:有关信号的完整列表,请参阅trap -l
。
在脚本退出时做一些事情
# Clear screen on script exit.
trap 'printf \\e[2J\\e[H\\e[m' EXIT
忽略终端中断(CTRL + C,SIGINT)
trap '' INT
对窗口调整大小做出反应
# Call a function on window resize.
trap 'code_here' SIGWINCH
在每个命令之前做点什么
trap 'code_here' DEBUG
当shell函数或源文件完成执行时执行某些操作
trap 'code_here' RETURN
性能
禁用Unicode
如果不需要unicode,则可以禁用它以提高性能。结果可能会有所不同,但是neofetch和其他程序有明显改善。
# Disable unicode.
LC_ALL=C
LANG=C
已过时的语法
家当
用#!/usr/bin/env bash
而不是#!/bin/bash
。
- 前者搜索用户
PATH
以查找bash
二进制文件。 - 后者假设它始终安装
/bin/
可能导致问题。
# Right:
#!/usr/bin/env bash
# Wrong:
#!/bin/bash
命令替换
用$()
而不是。
# Right.
var="$(command)"
# Wrong.
var=`command`
# $() can easily be nested whereas `` cannot.
var="$(command "$(command)")"
功能声明
不要使用function
关键字,它会降低与旧版本的兼容性bash
。
# Right.
do_something() {
# ...
}
# Wrong.
function do_something() {
# ...
}
内部变量
获取bash
二进制文件的位置
"$BASH"
获取当前正在运行的bash
进程的版本
# As a string.
"$BASH_VERSION"
# As an array.
"${BASH_VERSINFO[@]}"
打开用户首选的文本编辑器
"$EDITOR" "$file"
# NOTE: This variable may be empty, set a fallback value.
"${EDITOR:-vi}" "$file"
获取当前函数的名称
# Current function.
"${FUNCNAME[0]}"
# Parent function.
"${FUNCNAME[1]}"
# So on and so forth.
"${FUNCNAME[2]}"
"${FUNCNAME[3]}"
# All functions including parents.
"${FUNCNAME[@]}"
获取系统的主机名
"$HOSTNAME"
# NOTE: This variable may be empty.
# Optionally set a fallback to the hostname command.
"${HOSTNAME:-$(hostname)}"
获取操作系统的体系结构
"$HOSTTYPE"
获取操作系统/内核的名称
这可用于为不同的操作系统添加条件支持,
而无需调用uname
。
"$OSTYPE"
获取当前的工作目录
这是pwd
内置的替代品。
"$PWD"
获取脚本运行的秒数
"$SECONDS"
获取伪随机整数
每次$RANDOM
使用时,之间的不同整数0
及32767
被返回。此变量不应用于与安全性相关的任何内容(包括加密密钥等)。
"$RANDOM"
有关终端的信息
获取行和列中的终端大小(来自脚本)
在纯bash中编写脚本和stty
/ tput
无法
调用时,这很方便。
示例功能:
get_term_size() {
# Usage: get_term_size
# (:;:) is a micro sleep to ensure the variables are
# exported immediately.
shopt -s checkwinsize; (:;:)
printf '%s\n' "$LINES $COLUMNS"
}
用法示例:
# Output: LINES COLUMNS
$ get_term_size
15 55
以像素为单位获取终端大小
CAVEAT:这在某些终端仿真器中不起作用。
示例功能:
get_window_size() {
# Usage: get_window_size
printf '%b' "${TMUX:+\\ePtmux;\\e}\\e[14t${TMUX:+\\e\\\\}"
IFS=';t' read -d t -t 0.05 -sra term_size
printf '%s\n' "${term_size[1]}x${term_size[2]}"
}
用法示例:
# Output: WIDTHxHEIGHT
$ get_window_size
1200x800
# Output (fail):
$ get_window_size
x
获取当前光标位置
在纯bash中创建TUI时,这很有用。
示例功能:
get_cursor_pos() {
# Usage: get_cursor_pos
IFS='[;' read -p $'\e[6n' -d R -rs _ y x _
printf '%s\n' "$x $y"
}
用法示例:
# Output: X Y
$ get_cursor_pos
1 8
转换
将十六进制颜色转换为RGB
示例功能:
hex_to_rgb() {
# Usage: hex_to_rgb "#FFFFFF"
# hex_to_rgb "000000"
: "${1/\#}"
((r=16#${_:0:2},g=16#${_:2:2},b=16#${_:4:2}))
printf '%s\n' "$r $g $b"
}
用法示例:
$ hex_to_rgb "#FFFFFF"
255 255 255
将RGB颜色转换为十六进制
示例功能:
rgb_to_hex() {
# Usage: rgb_to_hex "r" "g" "b"
printf '#%02x%02x%02x\n' "$1" "$2" "$3"
}
用法示例:
$ rgb_to_hex "255" "255" "255"
#FFFFFF
CODE GOLF
更短的for
循环语法
# Tiny C Style.
for((;i++<10;)){ echo "$i";}
# Undocumented method.
for i in {1..10};{ echo "$i";}
# Expansion.
for i in {1..10}; do echo "$i"; done
# C Style.
for((i=0;i<=10;i++)); do echo "$i"; done
更短的无限循环
# Normal method
while :; do echo hi; done
# Shorter
for((;;)){ echo hi;}
更短的功能声明
# Normal method
f(){ echo hi;}
# Using a subshell
f()(echo hi)
# Using arithmetic
# This can be used to assign integer values.
# Example: f a=1
# f a++
f()(($1))
# Using tests, loops etc.
# NOTE: ‘while’, ‘until’, ‘case’, ‘(())’, ‘[[]]’ can also be used.
f()if true; then echo "$1"; fi
f()for i in "$@"; do echo "$i"; done
if
语法更短
# One line
# Note: The 3rd statement may run when the 1st is true
[[ $var == hello ]] && echo hi || echo bye
[[ $var == hello ]] && { echo hi; echo there; } || echo bye
# Multi line (no else, single statement)
# Note: The exit status may not be the same as with an if statement
[[ $var == hello ]] &&
echo hi
# Multi line (no else)
[[ $var == hello ]] && {
echo hi
# ...
}
case
设置变量的简单语句
在:
内置的可以用来避免重复variable=
在一个case语句。该$_
变量存储的最后一个命令的最后一个参数。:
总是成功,所以它可以用来存储变量值。
# Modified snippet from Neofetch.
case "$OSTYPE" in
"darwin"*)
: "MacOS"
;;
"linux"*)
: "Linux"
;;
*"bsd"* | "dragonfly" | "bitrig")
: "BSD"
;;
"cygwin" | "msys" | "win32")
: "Windows"
;;
*)
printf '%s\n' "Unknown OS detected, aborting..." >&2
exit 1
;;
esac
# Finally, set the variable.
os="$_"
其他
使用read
作为替代的sleep
命令
令人惊讶的是,sleep
是一个外部命令而不是bash
内置命令。
CAVEAT:需要bash
4+
示例功能:
read_sleep() {
# Usage: sleep 1
# sleep 0.2
read -rst "${1:-1}" -N 999
}
用法示例:
read_sleep 1
read_sleep 0.1
read_sleep 30
检查程序是否在用户的PATH中
# There are 3 ways to do this and either one can be used.
type -p executable_name &>/dev/null
hash executable_name &>/dev/null
command -v executable_name &>/dev/null
# As a test.
if type -p executable_name &>/dev/null; then
# Program is in PATH.
fi
# Inverse.
if ! type -p executable_name &>/dev/null; then
# Program is not in PATH.
fi
# Example (Exit early if program is not installed).
if ! type -p convert &>/dev/null; then
printf '%s\n' "error: convert is not installed, exiting..."
exit 1
fi
使用获取当前日期 strftime
Bash printf
有一个内置的获取日期的方法,可用于代替date
命令。
CAVEAT:需要bash
4+
示例功能:
date() {
# Usage: date "format"
# See: 'man strftime' for format.
printf "%($1)T\\n" "-1"
}
用法示例:
# Using above function.
$ date "%a %d %b - %l:%M %p"
Fri 15 Jun - 10:00 AM
# Using printf directly.
$ printf '%(%a %d %b - %l:%M %p)T\n' "-1"
Fri 15 Jun - 10:00 AM
# Assigning a variable using printf.
$ printf -v date '%(%a %d %b - %l:%M %p)T\n' '-1'
$ printf '%s\n' "$date"
Fri 15 Jun - 10:00 AM
获取当前用户的用户名
CAVEAT:需要bash
4.4+
$ : \\u
# Expand the parameter as if it were a prompt string.
$ printf '%s\n' "${_@P}"
black
生成UUID V4
CAVEAT:生成的值不具有加密安全性。
示例功能:
uuid() {
# Usage: uuid
C="89ab"
for ((N=0;N<16;++N)); do
B="$((RANDOM%256))"
case "$N" in
6) printf '4%x' "$((B%16))" ;;
8) printf '%c%x' "${C:$RANDOM%${#C}:1}" "$((B%16))" ;;
3|5|7|9)
printf '%02x-' "$B"
;;
*)
printf '%02x' "$B"
;;
esac
done
printf '\n'
}
用法示例:
$ uuid
d5b6c731-1310-4c24-9fe3-55d556d44374
进度条
这是一种绘制进度条的简单方法,无需
在函数本身中使用for循环。
示例功能:
bar() {
# Usage: bar 1 10
# ^----- Elapsed Percentage (0-100).
# ^-- Total length in chars.
((elapsed=$1*$2/100))
# Create the bar with spaces.
printf -v prog "%${elapsed}s"
printf -v total "%$(($2-elapsed))s"
printf '%s\r' "[${prog// /-}${total}]"
}
用法示例:
for ((i=0;i<=100;i++)); do
# Pure bash micro sleeps (for the example).
(:;:) && (:;:) && (:;:) && (:;:) && (:;:)
# Print the bar.
bar "$i" "10"
done
printf '\n'
获取脚本中的函数列表
get_functions() {
# Usage: get_functions
IFS=$'\n' read -d "" -ra functions < <(declare -F)
printf '%s\n' "${functions[@]//declare -f }"
}
绕过shell别名
# alias
ls
# command
# shellcheck disable=SC1001
\ls
绕过shell函数
# function
ls
# command
command ls
在后台运行命令
这将运行给定命令并使其保持运行,即使在终端或SSH连接终止后也是如此。忽略所有输出。
bkr() {
(nohup "$@" &>/dev/null &)
}
bkr ./some_script.sh # some_script.sh is now running in the background
参考
https://en.wikibooks.org/wiki/Bash_Shell_Scripting
后记
谢谢阅读
网友评论