Bash

Ctrl+D 产生 EOF 字符.

在当前shell上下文中执行文件.

source ./myfuncs
# or
. ./myfuncs

shell在登录时会从以下文件读取命令:

  • /etc/profile Bash的默认启动文件, 一定会被读取

按顺序查找以下文件, 运行第一个被找到的文件, 忽略其他文件:

  • $HOME/.bash_profile
  • $HOME/.bash_login
  • $HOME/.profile

用户自定义的配置文件, 通常由上述三个文件中的一个调用:

  • $HOME/.bashrc

eval 是bash的内建命令, 其作用等同于在命令行里输入和运行命令.
很少被使用.

type commandName # 查看指定命令是什么
type -a commandName # 查看该名称的命令的所有实现(一些命令有内建命令和二进制程序两种实现)
history # 查看历史命令列表, 配合grep, tail, head等命令使用
!! # 唤出刚刚调用过的命令
!20 # 唤出history列表里编号为20的命令
alias li='ls -li' # 创建别名
alias -p # 列出当前的别名

环境变量名称惯用大写字符. 用户自定义变量名称惯用小写字符.

var=value # 赋值
var = value # 相等判断
length=${#var} # 字符串长度

如果变量没有导出, 则子进程不会继承此变量.

导出后的函数或变量能够被扩展至子进程.

# 导出变量(变成当前进程的环境变量)
export var
export var='hello world'
# 导出函数
export -f fname

函数的参数需要从环境变量里取得,
使用的环境变量与命令行参数的环境变量一致.

function name {
commands
}
# 或者
name() { # ()仅表示这是一个函数, 无法在此定义参数
commands
}
local temp=$[ $value + 5 ]

return命令用于从函数中提前退出, 并返回特定的状态码.

如果函数没有return命令, 则会以最后一个命令的状态码作为函数的状态码.

array_var=(1 2 3 4 5 6)
array_var[0]='test1'
array_var[1]='test2'
echo $array_var # 只会打印第一个元素
echo ${array_var[0]}
echo ${array_var[$index]}
echo ${array_var[@]} # 打印出所有值 (方法1, 更常用)
echo ${array_var[*]} # 打印出所有值 (方法2, 输出结果为使用IFS的单个字符串)
echo ${!array_var[@]} # 数组索引列表 (方法1, 更常用)
echo ${!array_var[*]} # 数组索引列表 (方法2, 输出结果为使用IFS的单个字符串)
echo ${#array_var[*]} # 打印数组长度
unset array_var[2] # 删除数组下标为2的元素值(该下标不会被删除)
unset array_var # 删除数组
declare -A fruits_value # 声明关联数组
fruits_value=([apple]='100dollars' [orange]='150 dollars')
fruits_value[banana]='50 dollars'
echo "Apple costs ${fruits_value[apple]}"
echo ${!fruits_value[@]} # 关联数组索引 (方法1, 更常用)
echo ${!fruits_value[*]} # 关联数组索引 (方法2, 输出结果为使用IFS的单个字符串)

使用let命令时不需要在变量名之前加 $.

let result=num1+num2
let num++
let num--
let num+=6
let num+=6
let num-=6

使用 [] 操作符时, 变量名前的 $ 可以省略.
方括号操作符一般用于条件语句, 但也可用于数值计算.

result=$[ num1 + num2 ]
result=$[ $num + 5 ]
result=`expr 3 + 4`
result=$(expr $num + 5)

Bash的条件语句按照"表达式"的状态码决定真值或假值,
而非根据传统程序语言中的true和false.

if pwd; then
...
fi

Bash的 [] 方括号等价于用test命令进行条件测试.

if [ condition ]; then
...
elif [ condition ]; then
...
else
...
fi
[ n1 -eq n2 ] # 相等
[ n1 -ge n2 ] # 大于等于
[ n1 -gt n2 ] # 大于
[ n1 -le n2 ] # 小于等于
[ n1 -lt n2 ] # 小于
[ n1 -ne n2 ] # 不等于
[ -e file ] # 是否存在
[ -d file ] # 是否存在并是一个目录
[ -f file ] # 是否存在并使一个文件
[ -r file ] # 是否存在并可读
[ -s file ] # 是否存在并非空
[ -w file ] # 是否存在并可写
[ -x file ] # 是否存在并可执行
[ -O file ] # 是否存在并属于当前用户
[ -G file ] # 是否存在并属于当前用户的默认组(不会检查用户的所有组)
[ file1 -nt file2 ] # file1是否比file2新
[ file1 -ot file2 ] # file1是否比file2旧
[ condition1 ] && [ condition2 ]
[ condition1 ] || [ condition2 ]

双括号命令里可以使用高级数学运算, 可用于条件语句, 也可以用于单独的语句.
特殊符号在双括号命令里不需要转义.

(( val++ ))
(( val-- ))
(( ++val ))
(( --val ))
(( !val ))
(( ~val )) # 按位求反
(( val ** 2 )) # 幂运算
(( val << 2 )) # 位左移
(( val >> 2 )) # 位右移
(( val & 2 )) # 按位与
(( val | 2 )) # 按位或
(( val && 2 )) # 逻辑与

大部分字符串比较可以用单中括号 [] (test命令)完成.
但仍建议使用双中括号 [[]] (高级字符串比较),
因为使用Bash中的一些特殊符号例如 > 时不需要转义.

[[ "$str1" = "$str2" ]] # 相等
[[ "$str1" == "$str2" ]] # 相等
[[ "$str1" != "$str2" ]] # 不相等
[[ "$str1" > "$str2" ]] # 字母序大于
[[ "$str1" < "$str2" ]] # 字母序小于
[[ -z "$str" ]] # 空字符串
[[ -n "$str" ]] # 非空字符串
[[ $USER == r* ]] # (正则表达式)模式匹配(高级字符串比较独有)
case $VAR in
pattern1 | pattern2) commands1;;
pattern3) commands2;;
*) default commands;;
esac
repeat() {
while true; do
$@ && return
done
}

由于true在Bash里是一个二进制文件,
所以还有另一种使用 : 命令(返回0)的方法, 性能会更好:

repeat() {
while :; do
$@ && return
done
}

until循环的测试条件和while相反.

until condition; do
...
done

最后一个迭代的值会保留在循环变量里.

for var in list; do
commands
done

for循环使用特殊的环境变量IFS(内部字段分隔符, internal field separator)用于将字符串划分为多个元素.

OLD="$IFS"
IFS=$'\n'
for var in list; do
commands
done
IFS="$OLD"

C语言风格的循环变量无需 $ 符号.

for (( i = 1; i < 10; i++ )); do
commands
done
${param:+expression} # 当param不为空时, 使用expression的值
${param:-default} # 当param为空时, 使用default的值
${#param} # 给出param的长度
${filename%.*} # 提取文件名
${filename#*.} # 提取扩展名

从右向左匹配, 删除匹配到的字符串.

% 操作符的贪婪版本.

从左向右匹配, 删除匹配到的字符串.

# 运算符的贪婪版本.

环境变量PS1决定Bash的提示字符串(即在可输入区域前面的类似 [email protected]:~$ 的文本).

root用户的UID为0.

if [ $UID -ne 0 ]; then
echo 'Non-root user.'
else
echo 'Root user.'
fi
#!/bin/bash

调试用shebang:

#!bin/bash -xv
{name}={value} {program}

例如, 为sh运行脚本添加环境变量:

curl https://foo.bar/script.sh | TEST=1 sh
while IFS= read -r line; do
echo "$line"
done < "$file"
name="$0" # 脚本路径(调用时使用的路径)
first="$1"
second="$2"
tenth="${10}" # 10以后的参数需要用花括号
echo "$#" # 参数个数
echo "${!#}" # 最后一个参数, 当参数个数为零时, 返回的是脚本路径
echo "$@" # 以列表方式打印所有参数, 更常用
echo "$*" # 类似于[email protected], 但返回单一字符串, 输出基于IFS的第一个字符分隔的各个参数
for var in "$@"; do
echo "$var"
done

该命令用于去除已处理的参数, 直接调用即可左移一位.
如果带上数字 shift 2, 则左移相应位数.

shift命令可将 $3 的值移动到 $2 , 将 $2 移动到 $1,
删除 $1 的值(不会左移到 $0).

getopt optstring parameters

$ getopt ab:cd -a -b test1 -cd test2 test3
-a -b test1 -c -d -- test2 test3
# ab:cd是optstring, 冒号:说明b选项后有一个参数
# 如果指定了一个optstring不存在的选项, 则会出现错误(并且忽略掉此选项), 可在命令后加上-q选项忽略错误输出.
set -- $(getopt -q ab:cd "$@")
while [ -n "$1" ]; do
case "$1" in
-a) echo "Found the -a option";;
-b) param="$2"
echo "Found the -b option, with parameter value $param"
shift;;
-c) echo "Found the -c option";;
--) shift
break;;
*) echo "$1 is not an option";;
esac
shift
done

getopts与getopt的区别是, 后者输出一个字符串,
而前者一次处理一个参数(用于循环,
全部参数处理完后以非零状态码返回退出循环).

getopts optstring variable

如果optstring的第一个字符是=:=, 则忽略错误消息. 在处理过程中,
getopts使用两个环境变量: * OPTARG 如果选项有参数值, 则该变量保存参数值 *
OPTIND 参数位置

while getopts :ab:c opt
do
case "$opt" in
a) echo "Found the -a option";;
b) echo "Found the -b option, with value $OPTARG";;
c) echo "Found the -c option";;
*) echo "Unknown option: $opt";;
esac
done
  • -a 显示所有对象
  • -c 生成一个计数
  • -d 指定一个目录
  • -e 扩展一个对象
  • -f 指定读入数据的文件
  • -h 显示命令的帮助信息
  • -i 忽略文本大小写
  • -l 产生输出的长格式版本
  • -n 使用非交互模式(批处理)
  • -o 将所有输出重定向到的指定的输出文件
  • -q 以安静模式运行
  • -r 递归地处理目录和文件
  • -s 以安静模式运行
  • -v 生成详细输出
  • -x 排除某个对象
  • -y 对所有问题回答yes

可以通过在别名前添加 \ 来转义别名, 调用原本的命令.

\command

使用该选项在执行命令时显示参数和命令.

bash -x script.sh

通过 set -xset +x 限制调试区域, 只打印在此区域内的信息.

#!/bin/bash
#文件名: debug.sh
for i in {1..6};
do
set -x # 启用调试
echo $i
set +x # 禁用调试
done
echo "Script executed"

Bash的 : 命令相当于Python的 pass (什么也不做), 返回值(退出码)为0.

命令返回值为命令的退出状态码, 成功时为0, 最大值为255.

echo $?

使用 () 括起的内容会在子shell中运行, 如果在括号前加上 $,
则将子shell的运行结果返回.

可以在 () 里用 ; 分隔多个命令, 这样就可以在子shell中运行多个命令.

read var # 读取用户输入至变量var
read -n 2 var # 读取2个字符至变量var
read -s var # 以无回显的方式读取用户输入(密码)至变量var
read -p 'Enter input:' var # 在读取时显示提示信息
read -t 2 var # 读取2秒内输入的字符串至变量var
read -d ':' var # 以:为结束符, 读取之前的用户输入.
cat -s file

find命令的搜索条件是按参数顺序依次进行过滤的,
因此缩小搜索范围的参数应该尽可能放在前面, 否则会导致性能下降.

find /home/user -name '*.txt' -print # 打印用户主目录下所有.txt文件
find /home/user -iname '*.txt' -print # 上一行的忽略大小写版本
find .\(-name '*.txt' -o -name '*.pdf' \) -print # 使用OR条件语句(转义掉括号以免变成子shell命令)支持多个搜索条件
find /home -path '*/user/*' -print # 匹配路径
find /home -regex '.*/user/.*' -print # 匹配路径(基于正则表达式)
find /home -iregex '.*/user/.*' -print # 上一行的忽略大小写版本
find . ! -name '*.txt' # 查找当前目录下所有非.txt文件, !之后的条件都会变成非(not)
find . -maxdepth 1 -name '*.txt' -print # 最大目录深度1(当前目录)查找
find . -mindepth 2 -name '*.txt' -print # 最小目录深度2(当前目录的子目录)查找
find . -type d -print # 搜索目录
find . -type f -print # 搜索文件
find . -type f -atime -7 -print # 最近7天被访问过的文件(注意, atime属性在许多文件系统上都不准确)
find . -type f -atime 7 -print # 恰好7天前被访问过的文件
find . -type f -atime +7 -print # 上次访问时间超过7天的文件
# -mtime 文件数据的修改时间(仅在文件数据被修改时更新)
# -ctime 文件属性的修改时间(文件数据和元数据被修改时都会更新, 因此ctime一定不小于mtime)
# -amin 以分钟计的访问时间
find . -type f -newer file.txt -print # 找出比file.txt的修改时间更近的文件
find . -type f -size -2k # 小于2KB的文件
find . -type f -size 2k # 大于等于2KB的文件
# b 块(512字节)
# c 字节
# w 字(2字节)
# k 1024字节
# M 1024K字节
# G 1024M字节
find . -type f -name '*.swp' -delete # 删除当前目录下所有.swp文件
find . -type f -perm 644 -print # 打印权限为644的文件
find . -type f -user user -print # 打印特定用户拥有的文件
find . -type f -user root -exec chown user {} ';' # 找出所有root拥有的文件并执行命令改变其所有者, {}将自动填充为找到的单个文件名, ';'意为执行多条命令(也可以表示为\;)
find . -type f -exec echo {} + # 使用+将把所有文件名填充进来(也可以表示为\+和';'), exec命令只执行一次
find . -name '*.txt' -exec echo {} \; -exec grep banana {} \; # 执行多条命令
find . -name '*.txt' -exec sh -c "echo '{}' | grep banana" \; # 子shell(不使用子shell无法内嵌管道命令, 注意, 由于使用了""包装命令, 脚本中的到变量则需要转义$符号, 否则将变成读取当前shell的变量)
find . \(-name '.git' -prune \) -o \(-type f -print \) # 跳过(prune)所有.git目录
$ cat example.txt
1 2 3 4 5 6
7 8 9 10
11 12
$ cat example.txt | xargs
1 2 3 4 5 6 7 8 9 10 11 12
$ cat example.txt | xargs -n 3
1 2 3
4 5 6
7 8 9
10 11 12
$ echo 'splitXsplitXsplitXsplit' | xargs -d X
split split split split
$ echo 'splitXsplitXsplitXsplit' | xargs -d X -I {} echo {} # 指定用{}作为填充标志
split
split
split
split
$ find . -type f -name '*.txt' -print0 | xargs -0 rm -f # 结合find使用xargs, 用\0作为分隔符
echo 'HELLO WHO IS THIS' | tr 'A-Z' 'a-z' # 大写转小写
tr -d '0-9' # 删除stdin的数字
tr -d -c '0-9' # 删除stdin里非数字的内容(-c是补集的意思)
tr -s ' ' # 压缩所有重复的空格至只有一个空格
sha1sum filename
sha1sum -c filename.sha1 # 校验
sha1deep -rl dir > result.md5
# -r 递归
# -l 相对路径(默认情况输出绝对路径)
sort -t ':' -k 3 -n /etc/passwd # 以:为分隔符, 将第三列当作数字进行排序
sort -z data.txt | xargs -0
sort -C filename
if [ $? -eq 0 ]; then
echo 'Sorted'
else
echo 'Unsorted'
fi

uniq只能接受被sort命令排序过的输入.

uniq sorted.txt # 以唯一形式输出
uniq -c sorted.txt # 一并输出行的重复次数(作为第一列)
uniq -u sorted.txt # 只输出唯一行(有过重复的行不输出)
uniq -d sorted.txt # 只输出重复行(没有重复的行不输出)
uniq -z sorted.txt # 以\0为分隔符输出
filename=`mktemp` # 创建临时文件
dirname=`mktemp -d` # 创建临时目录
tmpfile=`mktemp -u` # 生成临时文件名(不创建临时文件)
mktemp test.XXX # 通过模板形式创建临时文件(模板至少要有3个X)
$ split -b 10k data.file # 单位 k, M, G, c, w
$ ls
data.file xaa xab xac xad xae xaf xag xah xai xaj
$ split -b 10k data.file -d -a 4 # 指定4位(-a 4)数字(-d)后缀
$ ls
data.file x0009 x0019 x0039 x0049 x0059 x0069 x0079
$ split -b 10k data.file -d -a 4 split_file # 指定文件前缀
$ ls
data.file split_file0002 split_file0005 split_file0008 strtok.c
split_file0000 split_file0003 split_file0006 split_file0009
split_file0001 split_file0004 split_file0007
$ split -l 10 data.file # 根据行数切割
rename *.JPG *.jpg
rename 's/ /_/g' *
rename 'y/A-Z/a-z/' *
rename 'y/a-z/A-Z/' *

使用 & 将命令转到后台运行, shell会打印 [{job_id}] {pid} (不能通过编程方式获取).
使用 $! 获取上一个命令的进程PID.
使用 wait 等待后台命令运行完成.

PIDARRAY=()
for file in File1.iso File2.iso
do
md5sum $file &
PIDARRAY+=("$!")
done
wait ${PIDARRAY[@]}

显示加号的作业被视作默认作业, 减号作业为下一个默认作业.
作业的id是从1开始的自增数字, 已用过的id会被后续的新作业重用.

jobs -l # 打印并显示PID
jobs -p # 只打印PID
jobs -r # 只列出运行中的作业
jobs -s # 只列出已停止的作业

通过 Ctrl+Z 暂停的作业可以用 bg 命令重启, 默认重启的是最近一个暂停的作业.
可以用 bg %{job_id} 手动指定作业的id.

bg 命令的前台版本, 作业将恢复到 Ctrl+Z 之前的状态.

与后台模式的区别在于协程会创建子shell, 且协程可以命名.

coproc sleep 10
coproc My_Job { sleep 10; } # 具名协程(花括号, 空格和;都是必须的)

dd生成文件时需要先在内存里申请一片内存空间.

缺点:

  • 在生成大文件时为了不爆内存, 只能循环写入小尺寸文件(通过count参数来循环写入指定次数), 不够直观.
  • 速度慢.
dd if=/dev/zero of=junk.data bs=1M count=1 # 生成1M文件

truncate创建的是文件系统上的稀疏文件(sparse file).
稀疏文件只有容量信息, 没有实际数据, 因此瞬间就能执行完毕.
稀疏文件对于程序来说通常是透明的, 仅在少数情况下与正常文件存在差异.

缺点:

  • truncate的实现方式使它上可以创建超过磁盘可用容量的文件.
truncate --size 1G junk.data

类似于truncate, 但不创建稀疏文件, 因此没有truncate的缺点.
fallocate创建的文件是从文件系统申请来的预分配空间, 因此也能很快创建.

缺点:

  • fallocate支持的文件系统有限, 但通常不会构成问题, 因为最流行的ext4被支持.
fallocate --length 1G junk.data

comm需要使用sort命令排序过的内容作为输入.

$ cat A.txt
apple
orange
gold
silver
steel
iron
$ cat B.txt
orange
gold
cookies
carrot
$ sort A.txt -o A.txt; sort B.txt -o B.txt
$ comm A.txt B.txt # 打印三列数据, 第一列为A.txt独有的行, 第二列为B.txt独有的行, 第三列为A.txt和B.txt共有的行
apple
carrot
cookies
gold
iron
orange
silver
steel
$ comm A.txt B.txt -1 -2 # 删除第一列和第二列, 只打印共有的行
$ comm A.txt B.txt -3 # 删除第三列, 打印A.txt独有的行(第一列)和B.txt独有的行(第二列)
$ comm A.txt B.txt -3 | sed 's/^\t//' # 上一条命令的单列版本(合并了两列内容)
lsof -p $$ # 列出指定PID的进程内的文件描述符, 环境变量$$表示当前PID
lsof -p $$ -d 0,1,2 # 列出编号为0,1,2的文件描述符

重定向输入(stdin) 0<
重定向输出(stdout) 1>
重定向错误(stderr) 2>

同时重定向输出和错误(stdout & stderr) &> 自定义重定向 3> 4<
重定向到文件描述符 3>&1 4<&0

用于脚本, 可以将某个文件描述符在脚本运行时永久重定向到某个文件.

重定向输出:

exec 1> testout
echo "This is a test of redirecting all output"
echo "from a script to another file."
echo "without having to redirect every individual line"

重定向输入:

exec 0< testfile # 将当前脚本的stdin忽略, 使用testfile作为stdin.
while read line do;
echo "$line"
done

关闭文件描述符:

exec 3>&-

该重定向定义一个结束字符串, 当stdin读取到结束字符串时, 读取完毕.
对于创建多行输入很有用.

$ wc << EOF
> test string 1
> test string 2
> test string 3
> EOF
3 9 42

Bash 3.x新增了字符串重定向操作符 <<<.

该操作符的常见使用场景是在不将命令结果输出到临时文件的情况下直接重定向输出结果到循环体:

done <<< "`find $path -type f -print"
$ date | tee testfile
$ date | tee -a testfile # 追加到文件

文件1:version1.txt

this is the original text
line2
line3
line4
happy hacking !

文件2:version2.txt

this is the original text
line2
line4
happy hacking !
GNU is not UNIX
$ diff -u version1.txt version2.txt # 生成一体化输出, 因为此输出结果人类可读性比较好
--- version1.txt 2010-06-27 10:26:54.384884455 +0530
+++version2.txt 2010-06-27 10:27:28.782140889 +0530
@@ -1,5 +1,5 @@
this is the original text
line2
-line3
line4
happy hacking !
-
+GNU is not UNIX

diff 是基于行进行比较的, 第一个文件被认为是旧文件,
第二个文件被认为是新文件.

+ 开头的行是新加入的行.
- 开头的行是删除的行.

diff -u version1.txt version2.txt > version.patch
patch version1.txt < version.patch

打补丁时, -p{num} 参数表示补丁文件里应该跳过的目录前缀,
假设patch里的文件名为 /u/howard/src/blurfl/blurfl.c.
-p0 表示不跳过,
使用相同的文件名.
-p1 表示去掉一层, 返回 u/howard/src/blurfl/blurfl.c.
-p2 表示去掉两层, 返回 howard/src/blurflc/blurfl.c, 以此类推.

如果一个文件已经被打补丁, 那么直接运行打补丁文件会提示是否要撤销补丁.
也可以手动加上 -R 参数表示撤销补丁.

patch -R version1.txt < version.patch
diff -Naur directory1 directory2
  • -N 所有缺失的文件视作空文件
  • -a 所有文件都被视为文本文件
  • -u 一体化输出
  • -r 递归

建议使用ripgrep替代(命令为rg), 速度更快且默认参数更符合需求.

grep -E '[a-z]+' filename # 使用POSIX扩展正则表达式
egrep '[a-z]+' filename # [不推荐]egrep是grep的正则匹配直接调用命令, 已被弃用, 仅用于兼容旧脚本
grep -E 'pattern1|pattern2' filename # 搜索多个模式
grep -v match_pattern file # 打印除了匹配项以外的行
grep -R 'text' . # 递归搜索文件
grep -i 'HELLO' file # 忽略大小写
grep -r --include *.{c,cpp} 'main()' . # 递归搜索c, cpp文件
grep -r --exclude 'README' 'main()' . # 排除README文件
grep -n match_pattern file # 输出时打印行号
grep -c match_pattern file # 输出符合匹配的行数
grep -B 3 match_pattern file # 打印结果时一并输出匹配项的前3行
grep -A 3 match_pattern file # 打印结果时一并输出匹配项的后3行
grep -C 3 match_pattern file # 打印结果时一并输出匹配项的前3行和后3行
grep -e pattern1 -e pattern2 file # 指定多个模式
cut -f 2,3 filename # 显示文件的第二列和第三列
cut -d ':' -f 2,3 filename # 指定文件的分隔符, 显示文件的第二列和第三列
tail -5 file # 显示文件最后5行
tail -f # 持续打印文件尾行追加的内容(用于监视日志)

ps命令有3种不同类型的命令行参数: Unix风格, 参数使用单破折号 BSD风格,
参数不加破折号 GNU风格, 参数前加双破折号

建议使用Unix风格.

ps -ef # 打印进程
ps -ef --forest # 以树形打印进程

top是一个交互式命令行程序.

f 选择排序字段.
d 修改轮询间隔.
q 退出.

建议使用htop.

killall http* # 支持以进程名通配符批量结束进程

常见信号:

  • 1 SIGHUP 挂起
  • 2 SIGINT 终止
  • 3 SIGQUIT 停止
  • 15 SIGTERM 尽可能终止
  • 17 SIGSTOP 无条件停止, 但不是终止
  • 18 SIGTSTP 停止或暂停进程
  • 19 SIGCONT 继续运行停止的进程

Ctrl+C 生成SIGINT信号
Ctrl+Z 生成SIGTSTP信号

kill -STOP {pid}
kill -KILL {pid}

trap "echo 'Sorry! I have trapped Ctrl-C'" SIGINT

用nohup命令可以阻断SIGHUP信号, 这会导致该进程解除与当前终端的关联,
即使终端退出也不会停止运行.

$ nohup ./test1.sh &
df -h # 以人类可读方式打印
df -T # 打印文件系统类型
tar -cf target.tar file1 file2 # 打包文件
tar -czf target.tar file1 file2 # 打包文件并以gzip压缩
tar -xf source.tar # 解压文件
tar -xf source.tar -C directory # 解压文件至指定目录
readlink /usr/bin/vi # 找出vi的链接目标
readlink -f /usr/bin/vi # 找出vi最终的链接目标(连续的链接)

Bash管道并非顺序运行, 而是管道中的每个命令都并行运行,
前一个命令的stdout将实时成为后一个命令的stdin, 其中没有任何缓冲区.

每个月最后一天的定期作业

00 12 * * * if [ `date +%d -d tomorrow` = 01 ]; then; command
ls /etc/cron.*ly # 列出cron目录里的任务
crontab -l # 列出已有的cron时间表
5 * * * * 每小时的第5分钟
*/5 * * * * 每5分钟
0,5 * * * * 每消失的第0分钟和第5分钟
select variable in list; do
commands
done
PS3='Enter option: ' # 该环境变量决定select的提示符
select option in 'Display disk space' 'Display logged on users' 'Display memory usage' 'Exit program'
; do
case "$option" in
'Exit program') break;;
'Display disk space') break;;
'Display logged on users') break;;
'Display memory usage') break;;
*) clear
esac
done
shopt -s extglob # 开启extglob

?(pattern-list) 匹配0或1个模式, 等价于正则表达式 (...|...)?
*(pattern-list) 匹配0或多个模式, 等价于正则表达式 (...|...)*
+(pattern-list) 匹配1或多个模式, 等价于正则表达式 (...|...)+
@(pattern-list) 匹配其中之一个模式, 等价于正则表达式 (...|...)
!(pattern-list) 匹配除了列出的模式以外的任何东西, 等价于正则表达式的否定断言

否定匹配很容易错误使用, 详见下方例子.

# 假设我们需要列出所有.jpg和.gif扩展名以外的文件.
ls *!(.jpg|.gif)
# 该匹配不起作用, 因为模式最前面的*会匹配任意模式
ls !(*.jpg|*.gif)
# 起作用的匹配

用于命令行的TUI工具, 它在底层使用了ncurses库以在终端提供可交互的图形界面.

所有widget类型的截图:
https://invisible-island.net/dialog/dialog-figures.html

TUI给人的感觉太重, 因为TUI会占据整个终端的界面.