🌱 🦤 🌱

linux常用命令

ls

1
ls -alF
1
2
3
4
5
6
-代表文件
d 代表目录
l 代表链接
c 代表字符型设备
b 代表块设备
n 代表网络设备

ln

1
touch data_file
1
ln -s data_file  sl_data_file
1
ls -il *data_file
1
readlink -f sl_data_file
1
rm *data_file

ps top

1
ps l --forest
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
F:内核分配给进程的系统标记。1 代表 进程被 fork 但没有被执行。4 代表使用了超级管理员的权限。5 代表 1 和 4 都做了。0 没有任何特殊含义,含义为进程被 fork 了,也确实执行了,并且没有超级用户权限。
UID:启动这些进程的用户。
PID:进程的进程 ID。
PPID:父进程的进程号(如果该进程是由另一个进程启动的)。
PRI:进程的优先级(越大的数字代表越低的优先级)。
NI:谦让度值用来参与决定优先级。越大优先级越低。
VSZ:进程的虚拟内存大小,以千字节(KB)为单位。
RSS:常驻集大小,进程在未换出时占用的物理内存。
WCHAN:进程休眠的内核函数的地址。
STAT:代表当前进程状态的双字符状态码。
TTY:进程启动时的终端设备。
TIME:运行进程需要的累计 CPU 时间。

<:该进程运行在高优先级上。
N:该进程运行在低优先级上。
L:该进程有页面锁定在内存中。
s:该进程是控制进程。
l:该进程是多线程的。
+:该进程运行在前台。
1
top
1
键入 f 允许你选择对输出进行排序的字段,键入 d 允许你修改轮询间隔。键入 q 可以退出 top。

df

1
df -h

du

1
du -shc *
1
du -s * | sort -nr

grep

1
grep [options] pattern [file]

内建命令和外部命令

1
type -a echo
1
which echo

命令历史记录

1
!!
1
history -a
1
!1
1
cat ~/.zsh_history | less

命令别名

1
alias gitacp='git add . && git commit -am "." && git push origin master'

文件权限

1
touch newfile
1
umask
1
2
3
第一位仅代表 c 语言习惯的前导八进制 0,并不起实际作用
后面的 3 位表示文件或目录对应的 umask 八进制值
这里的减去描述的意思为按位去除 umask 的权限,并不是真正的减法
1
chmod 750 testfile
1
chmod u+x testfile
1
2
3
4
u 代表用户
g 代表组
o 代表其他
a 代表上述所有

环境变量

  • 如果要用到变量,使用$;如果要操作变量,不使用$。这条规则的一个例外就是使用 printenv 显示某个变量的值

全局变量

1
printenv HOME
1
echo $HOME
1
PATH=$PATH:.

局部变量

1
2
3
4
echo $my_var
my_var=666
echo $my_var
unset my_vay
1
export my_var

环境变量持久化

1
file ~/.bashrc
1
file ~/.zshrc

shell相关

shell父子关系

1
echo $ZSH_SUBSHELL
1
(echo $ZSH_SUBSHELL)

后台运行

1
sleep 3000 &
1
jobs -l
1
kill -9 pid

协程

1
coproc sleep 10
1
coproc My_Job { sleep 10; }

脚本常识

1
#!/bin/zsh
  • 命令输出赋值, 数学运算赋值, 双括号高级数学表达式赋值
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#!/bin/bash

var=`whoami`; echo $var

var=` whoami `; echo $var

var=$(whoami); echo $var

var=$( whoami ); echo $var


var=$[(1 + 5) * 2]; echo $var

var=$[ (1 + 5) * 2 ]; echo $var


(( var = 2 ** 3 )); echo $var

((var = 2 ** 3)); echo $var
1
2
3
4
5
6
7
8
admin
admin
admin
admin
12
12
8
8
  • 测试命令
1
2
3
4
5
6
7
8
9
10
11
12
13
#!/bin/bash

if test 0; then
echo "done"
else
echo "not"
fi

if [ a \> A ] && [ 1 \< 2 ]; then
echo "done"
else
echo "not"
fi
1
2
done
done
  • 双括号: 高级数学表达式
  • 双方括号: 字符串比较高级特性
1
2
3
4
5
6
7
8
9
10
11
12
13
#!/bin/bash

if (( 2 > 1 )) && ((3 ** 3 > 1)); then
echo "done"
else
echo "not"
fi

if [[ $USER == a* ]]; then
echo "done"
else
echo "not"
fi
1
2
done
done

命令替换

  • 可以从命令输出中提取信息,并将其赋给变量。把输出赋给变量之后,就可以随意在脚本中使用了
1
var=`date`; echo $var
1
var=$(date); echo $var
  • 命令替换会创建一个子 shell 来运行对应的命令。子 shell(subshell)是由运行该脚本的 shell 所创建出来的一个独立的子 shell(child shell)。正因如此,由该子 shell 所执行命令是无法使用脚本中所创建的变量的。

执行数学运算

  • 在 bash 中,在将一个数学运算结果赋给某个变量时,可以用美元符和方括号($[ operation ])将数学表达式围起来。

  • 注意在使用方括号来计算公式时,不用担心 shell 会误解乘号或其他符号。shell 知道它不是通配符,因为它在方括号内。

1
var=$[ (1 + 5) * 2 ]; echo $var
  • bash shell 数学运算符只支持整数运算。若要进行任何实际的数学计算,这是一个巨大的限制

退出脚本

  • Linux 提供了一个专门的变量$?来保存上个已执行命令的退出状态码。对于需要进行检查的命令,必须在其运行完毕后立刻查看或使用$?变量。它的值会变成由 shell 所执行的最后一条命令的退出状态码。
1
echo $?
1
2
3
4
5
6
7
8
9
0 命令成功结束
1 一般性未知错误
2 不适合的 shell 命令
126 命令不可执行
127 没找到命令
128 无效的退出参数
128+x 与 Linux 信号 x 相关的严重错误
130 通过 Ctrl+C 终止的命令
225 正常范围之外的退出状态码
  • exit 命令允许你在脚本结束时指定一个退出状态码。
1
exit 1

test命令

  • test 命令提供了在 if-then 语句中测试不同条件的途径

  • 如果 test 命令中列出的条件成立,test 命令就会退出并返回退出状态码 0。

  • 如果条件不成立,test 命令就会退出并返回非零的退出状态码,这使得 if-then 语句不会再被执行。

1
2
3
4
5
if test 0; then
echo "done"
else
echo "not"
fi
  • 如果只执行 test 命令本身,不写 test 命令的条件部分,它会以非零的退出状态码退出,并执行 else 语句块。
1
test
  • bash shell 提供了另一种条件测试方法,无需在 if-then 语句中声明 test 命令。

  • 方括号定义了测试条件,是与 test 命令同义的特殊 bash 命令。注意,第一个方括号之后和第二个方括号之前必须加上一个空格,否则就会报错。

  • 方括号定义了测试条件,是与 test 命令同义的特殊 bash 命令。

1
2
3
4
if [ condition ]
then
commands
fi
  • 数值比较
1
2
3
4
5
6
n1 -eq n2 检查 n1 是否与 n2 相等
n1 -ge n2 检查 n1 是否大于或等于 n2
n1 -gt n2 检查 n1 是否大于 n2
n1 -le n2 检查 n1 是否小于或等于 n2
n1 -lt n2 检查 n1 是否小于 n2
n1 -ne n2 检查 n1 是否不等于 n2
  • 字符串比较
1
2
3
4
5
6
str1 = str2 检查 str1 是否和 str2 相同
str1 != str2 检查 str1 是否和 str2 不同
str1 \< str2 检查 str1 是否比 str2 小
str1 \> str2 检查 str1 是否比 str2 大
-n str1 检查 str1 的长度是否非 0
-z str1 检查 str1 的长度是否为 0
  • 文件比较
1
2
3
4
5
6
7
8
9
10
11
-d file 检查 file 是否存在并是一个目录
-e file 检查 file 是否存在(文件或目录)
-f file 检查 file 是否存在并是一个文件
-r file 检查 file 是否存在并可读
-s file 检查 file 是否存在并非空
-w file 检查 file 是否存在并可写
-x file 检查 file 是否存在并可执行
-O file 检查 file 是否存在并属当前用户所有
-G file 检查 file 是否存在并且默认组与当前用户相同
file1 -nt file2 检查 file1 是否比 file2 新
file1 -ot file2 检查 file1 是否比 file2 旧

复合条件测试

1
2
[ condition1 ] && [ condition2 ]
[ condition1 ] || [ condition2 ]
1
2
3
4
5
if [ a \> A ] && [ 1 \< 2 ]; then
echo "done"
else
echo "not"
fi

使用双括号

  • 双括号命令允许你在比较过程中使用高级数学表达式。test 命令只能在比较中使用简单的算术操作。双括号命令提供了更多的数学符号,这些符号对于用过其他编程语言的程序员而言并不陌生。除了 test 命令使用的标准数学运算符,如下列出了双括号命令中还可以使用的其他运算符。
1
2
3
4
5
6
7
8
9
10
11
12
13
val++     # 后增
val-- # 后减
++val # 先增
--val # 先减
! # 逻辑求反
~ # 位求反
** # 幂运算
<< # 左位移
>> # 右位移
& # 位布尔和
| # 位布尔或
&& # 逻辑和
|| # 逻辑或
1
2
3
4
5
if (( 2 > 1 )) && ((3 ** 3 > 1)); then
echo "done"
else
echo "not"
fi
1
(( var = 2 ** 3 )); echo $var

使用双方括号

  • 双方括号命令提供了针对字符串比较的高级特性。双方括号使用了 test 命令中采用的标准字符串比较。但它提供了 test 命令未提供的另一个特性——模式匹配(pattern matching)。

  • 不过要小心,不是所有的 shell 都支持双方括号。

1
2
3
4
5
if [[ $USER == a* ]]; then
echo "done"
else
echo "not"
fi

更改字段分隔符

  • IFS 环境变量定义了 bash shell 用作字段分隔符的一系列字符。

  • 默认情况下,bash shell 会将下列字符当作字段分隔符:空格, 制表符, 换行符

1
IFS=$'\n'
1
IFS=:
1
IFS=$'\n':;"
  • 在改变 IFS 之前保存原来的 IFS 值,之后再恢复它
1
2
3
IFS.OLD=$IFS
IFS=$'\n'
IFS=$IFS.OLD
  • 在 Linux 中,目录名和文件名中包含空格当然是合法的。要适应这种情况,应该将$file 变量用双引号圈起来。如果不这么做,遇到含有空格的目录名或文件名时就会有错误产生。
1
if [ -d "$file" ]

脚本结构化命令

if

1
2
3
4
5
6
7
8
9
10
11
12
13
#!/bin/bash

v1=""
v2=1
v3=2

if test $v1; then
echo "111"
elif [ $v2 -le $v3 ] && [ $v2 -le 3 ]; then
echo "222"
else
echo "999"
fi
1
222

case

  • case 命令会将指定的变量与不同模式进行比较。如果变量和模式是匹配的,那么 shell 会执行为该模式指定的命令。可以通过竖线操作符在一行中分隔出多个模式模式。星号会捕获所有与已知模式不匹配的值。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#!/bin/bash

var="admin"

case $var in
admin1 | admin2)
echo "hi"
echo "1 or 2";;
admin3)
echo "hi"
echo "3";;
*)
echo "hi"
echo "none"
esac
1
2
hi
none

for

1
2
3
4
5
6
7
#!/bin/bash

list="1 2 3 5 6 7 8 9"
list=$list" 0"
for _ in $list; do
echo "$_"
done
1
2
3
4
5
6
7
8
9
1
2
3
5
6
7
8
9
0

C 语言风格的 for

1
2
3
4
5
#!/bin/bash

for (( a=1, b=9; a <= 3; a++, b-- )); do
echo "$a - $b"
done
1
2
3
1 - 9
2 - 8
3 - 7

while

  • while 命令允许你在 while 语句行定义多个测试命令。只有最后一个测试命令的退出状态码会被用来决定什么时候结束循环。
1
2
3
4
5
6
7
8
9
#!/bin/bash

var1=9
while echo $var1
[ $var1 -ge 0 ]
do
echo Inside the loop: $var1
var1=$[ $var1 - 1 ]
done
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
9
Inside the loop: 9
8
Inside the loop: 8
7
Inside the loop: 7
6
Inside the loop: 6
5
Inside the loop: 5
4
Inside the loop: 4
3
Inside the loop: 3
2
Inside the loop: 2
1
Inside the loop: 1
0
Inside the loop: 0
-1

until

  • 只有测试命令的退出状态码不为 0,bash shell 才会执行循环中列出的命令。
  • 和 while 命令类似,你可以在 until 命令语句中放入多个测试命令。只有最后一个命令的退出状态码决定了 bash shell 是否执行已定义的 other commands。
1
2
3
4
5
6
7
8
9
#!/bin/bash

var1=100
until echo $var1
[ $var1 -eq 0 ]
do
echo Inside the loop: $var1
var1=$[ $var1 - 25 ]
done
1
2
3
4
5
6
7
8
9
100
Inside the loop: 100
75
Inside the loop: 75
50
Inside the loop: 50
25
Inside the loop: 25
0

控制循环

1
break
1
break 1
1
break n
1
continue
1
continue n

处理循环的输出

1
2
3
4
5
#!/bin/bash

for (( a=1; a <=9; a++ )); do
echo "$a"
done | sort -r
1
2
3
4
5
6
7
8
9
9
8
7
6
5
4
3
2
1

查找可执行文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#!/bin/bash

IFS=:
for folder in $PATH
do
echo "$folder:"
for file in $folder/*
do
if [ -x $file ]
then
echo " $file"
fi
done
done

处理输入

  • bash shell 会将一些称为位置参数(positional parameter)的特殊变量分配给输入到命令行中的所有参数。这也包括 shell 所执行的脚本名称。位置参数变量是标准的数字:$0 是程序名,$1 是第一个参数,$2 是第二个参数,依次类推,直到第九个参数$9。

  • basename 命令会返回不包含路径的脚本名

1
name=$(basename $0)
  • 特殊变量$#含有脚本运行时携带的命令行参数的个数
1
echo $#
  • 在使用参数前一定要检查其中是否存在数据
1
if [ -n "$1" ]
1
if [ $# -ne 2 ]

参数统计

1
2
3
4
5
#!/bin/bash

params=$#
echo The last parameter is $params
echo The last parameter is ${!#}
1
2
3
4
┌─(~/test)──────────────────────────────────────────────────────────(admin@miniarch:pts/1)─┐
└─(00:00:00 on master ✹)──> test.sh 1 2 3 ──(一,1月01)─┘
The last parameter is 3
The last parameter is 3

抓取所有的数据

  • $*和$@变量可以用来轻松访问所有的参数

  • $*变量会将命令行上提供的所有参数当作一个单词保存

  • $@变量会将命令行上提供的所有参数当作同一字符串中的多个独立的单词

  • $*变量会将所有参数当成单个参数,而$@变量会单独处理每个参数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#!/bin/bash

echo
count=1
for param in "$*"
do
echo "\$* $count = $param"
count=$[ $count + 1 ]
done



echo
count=1
for param in "$@"
do
echo "\$@ $count = $param"
count=$[ $count + 1 ]
done
1
2
3
4
5
6
7
8
┌─(~/test)──────────────────────────────────────────────────────────(admin@miniarch:pts/1)─┐
└─(00:00:00 on master ✹)──> test.sh 1 2 3 ──(一,1月01)─┘

$* 1 = 1 2 3

$@ 1 = 1
$@ 2 = 2
$@ 3 = 3

移动变量

  • 在使用 shift 命令时,默认情况下它会将每个参数变量向左移动一个位置。所以,变量$3 的值会移到$2 中,变量$2 的值会移到$1 中,而变量$1 的值则会被删除(注意,变量$0 的值,也就是程序名,不会改变)

  • shift 2一次性移动两个位置

1
2
3
4
5
6
7
8
9
#!/bin/bash

count=1
while [ -n "$1" ]
do
echo "Parameter $count = $1"
count=$[ $count + 1 ]
shift
done
1
2
3
4
5
┌─(~/test)──────────────────────────────────────────────────────────(admin@miniarch:pts/1)─┐
└─(00:00:00 on master ✹)──> test.sh 1 2 3 ──(一,1月01)─┘
Parameter 1 = 1
Parameter 2 = 2
Parameter 3 = 3

getopt处理选项

1
getopt ab:cd -a -b test1 -cd test2 test3
1
-a -b test1 -c -d -- test2 test3
1
getopt -q ab:cd -a -b test1 -cde test2 test3
1
-a -b 'test1' -c -d -- 'test2' 'test3'
  • 可以在脚本中使用 getopt 来格式化脚本所携带的任何命令行选项或参数

  • set 命令的选项之一是双破折线(–),它会将命令行参数替换成 set 命令的命令行值

1
set -- $(getopt -q ab:cd "$@")
  • getopt 命令并不擅长处理带空格和引号的参数值。它会将空格当作参数分隔符,而不是根据双引号将二者当作一个参数

使用更高级的 getopts

  • 如果选项字母要求有个参数值,就加一个冒号

  • 要去掉错误消息的话,可以在开头加一个冒号

  • getopts 命令会用到两个环境变量。如果选项需要跟一个参数值,OPTARG 环境变量就会保存这个值。OPTIND 环境变量保存了参数列表中 getopts 正在处理的参数位置。这样你就能在处理完选项之后继续处理其他命令行参数了

1
2
3
4
5
6
7
8
9
10
11
#!/bin/bash

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
1
2
3
4
┌─(~/test)──────────────────────────────────────────────────────────(admin@miniarch:pts/1)─┐
└─(00:00:00 on master ✹)──> test.sh -b "test1 test2" -a ──(一,1月01)─┘
Found the -b option, with value test1 test2
Found the -a option
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
while getopts :ab:cd opt
do
echo $OPTIND
case "$opt" in
a) echo "Found the -a option" ;;
b) echo "Found the -b option, with value $OPTARG" ;;
c) echo "Found the -c option" ;;
d) echo "Found the -d option" ;;
*) echo "Unknown option: $opt" ;;
esac
done

shift $[ $OPTIND - 1 ]

echo
count=1
for param in "$@"
do
echo "Parameter $count: $param"
count=$[ $count + 1 ]
done
1
2
3
4
5
6
7
8
9
10
11
12
┌─(~/test)──────────────────────────────────────────────────────────(admin@miniarch:pts/1)─┐
└─(00:00:00 on master ✹)──> test.sh -a -b test1 -d test2 test3 ──(一,1月01)─┘
2
Found the -a option
4
Found the -b option, with value test1
5
Found the -d option

Parameter 1: test2
Parameter 2: test3
Parameter 3: test4

将选项标准化

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
-a 显示所有对象
-c 生成一个计数
-d 指定一个目录
-e 扩展一个对象
-f 指定读入数据的文件
-h 显示命令的帮助信息
-i 忽略文本大小写
-l 产生输出的长格式版本
-n 使用非交互模式(批处理)
-o 将所有输出重定向到的指定的输出文件
-q 以安静模式运行
-r 递归地处理目录和文件
-s 以安静模式运行
-v 生成详细输出
-x 排除某个对象
-y 对所有问题回答 yes

获得用户输入

  • echo 命令使用了-n 选项。该选项不会在字符串末尾输出换行符,允许脚本用户紧跟其后输入数据,而不是下一行

  • -p 选项,允许你直接在 read 命令行指定提示符

  • 用-t 选项来指定一个计时器。-t 选项指定了 read 命令等待输入的秒数。当计时器过期后,read 命令会返回一个非零退出状态码

  • -s 选项可以避免在 read 命令中输入的数据出现在显示器上(实际上,数据会被显示,只是 read 命令会将文本颜色设成跟背景色一样)

  • 将-n 选项和值 1 一起使用,告诉 read 命令在接受单个字符后退出。只要按下单个字符回答后,read 命令就会接受输入并将它传给变量,无需按回车键

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
#!/bin/bash

echo
echo -n "Enter your name: "
read name
echo $name

echo
read -p "Enter age, first name, last name: " first last
echo $last, $first

echo
read -p "Enter any: "
echo $REPLY

echo
if read -t 5 -p "Please enter your name: " name
then
echo "Hello $name, welcome to my script"
else
echo "Sorry, too slow! "
fi

echo
read -s -p "Enter your password: " pass
echo
echo "Is your password really $pass? "

echo
read -n1 -p "Do you want to continue [Y/N]? " answer
case $answer in
Y | y) echo
echo "fine, continue on...";;
N | n) echo
echo OK, goodbye
exit;;
esac
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
┌─(~/test)──────────────────────────────────────────────────────────(admin@miniarch:pts/1)─┐
└─(00:00:00 on master ✹)──> test.sh ──(一,1月01)─┘

Enter your name: name
name

Enter age, first name, last name: 1 m e
m e, 1

Enter any: any
any

Please enter your name: Sorry, too slow!

Enter your password:
Is your password really passwd?

Do you want to continue [Y/N]? n
OK, goodbye

处理输出

标准文件描述符

  • Linux 系统将每个对象当作文件处理。这包括输入和输出进程。Linux 用文件描述符(filedescriptor)来标识每个文件对象。文件描述符是一个非负整数,可以唯一标识会话中打开的文件。每个进程一次最多可以有九个文件描述符。出于特殊目的,bash shell 保留了前三个文件描述符(0、1 和 2)。这三个特殊文件描述符会处理脚本的输入和输出。
1
2
3
0 缩写:STDIN 含义:标准输入
1 缩写:STDOUT 含义:标准输出
2 缩写:STDERR 含义:标准错误

重定向错误

1
ls -al test err 2> err.txt 1>out.txt
  • 也可以将 STDERR 和 STDOUT 的输出重定向到同一个输出文件。为此 bash shell 提供了特殊的重定向符号&>
1
ls -al test err &> log.txt
  • 为了避免错误信息散落在输出文件中,相较于标准输出,bash shell 自动赋予了错误消息更高的优先级。这样你能够集中浏览错误信息了

在脚本中重定向输出

  • 默认情况下,Linux 会将 STDERR 导向 STDOUT。但是,如果你在运行脚本时重定向了 STDERR,脚本中所有导向 STDERR 的文本都会被重定向。
1
2
3
4
5
6
7
#!/bin/bash

exec 2>testerror
echo "1"
exec 1>testout
echo "2"
echo "3" >&2
1
2
3
4
5
6
7
8
9
┌─(~/test)──────────────────────────────────────────────────────────(admin@miniarch:pts/1)─┐
└─(00:00:00 on master ✹)──> test.sh ──(一,1月01)─┘
1
┌─(~/test)──────────────────────────────────────────────────────────(admin@miniarch:pts/1)─┐
└─(00:00:00 on master ✹)──> cat testout ──(一,1月01)─┘
2
┌─(~/test)──────────────────────────────────────────────────────────(admin@miniarch:pts/1)─┐
└─(00:00:00 on master ✹)──> cat testerror ──(一,1月01)─┘
3

创建自己的重定向

1
exec 3>testme
  • 使用 exec 命令来将输出追加到现有文件中
1
exec 3>>testout
  • 可以将 STDOUT 的原来位置重定向到另一个文件描述符,然后再利用该文件描述符重定向回 STDOUT
1
2
3
4
5
6
7
8
9
10
11
#!/bin/bash

# 3 显示器
# 1 testout文件
# 1 显示器

exec 3>&1
exec 1>testout
echo "This should store in the output file"
exec 1>&3
echo "Now things should be back to normal"
1
2
3
4
5
6
┌─(~/test)──────────────────────────────────────────────────────────(admin@miniarch:pts/1)─┐
└─(00:00:00 on master ✹)──> test.sh ──(一,1月01)─┘
Now things should be back to normalc
┌─(~/test)──────────────────────────────────────────────────────────(admin@miniarch:pts/1)─┐
└─(00:00:00 on master ✹)──> cat testout ──(一,1月01)─┘
This should store in the output file
  • 可以用和重定向输出文件描述符同样的办法重定向输入文件描述符
1
2
3
exec 6<&0
exec 0< testfile
exec 0<&6
  • 可以打开单个文件描述符来作为输入和输出。可以用同一个文件描述符对同一个文件进行读写。不过用这种方法时,你要特别小心。由于你是对同一个文件进行数据读写,shell 会维护一个内部指针,指明在文件中的当前位置。任何读或写都会从文件指针上次的位置开始
1
exec 3<> testfile
  • 如果你创建了新的输入或输出文件描述符,shell 会在脚本退出时自动关闭它们。然而在有些情况下,你需要在脚本结束前手动关闭文件描述符。

  • 一旦关闭了文件描述符,就不能在脚本中向它写入任何数据,否则 shell 会生成错误消息。

1
exec 3>&c

列出打开的文件描述符

  • lsof 命令会列出整个 Linux 系统打开的所有文件描述符
1
lsof -a -p $$ -d 0,1,2

阻止命令输出

  • 有时候,你可能不想显示脚本的输出。这在将脚本作为后台进程运行时很常见,尤其是当运行会生成很多烦琐的小错误的脚本时。要解决这个问题,可以将 STDERR 重定向到一个叫作 null 文件的特殊文件
1
ls -al > /dev/null
  • 文件 testfile 仍然存在系统上,但现在它是空文件。这是清除日志文件的一个常用方法,因为日志文件必须时刻准备等待应用程序操作。
1
cat /dev/null > testfile

创建临时文件

  • 有个特殊命令可以用来创建临时文件。mktemp 命令可以在/tmp 目录中创建一个唯一的临时文件。shell 会创建这个文件,但不用默认的 umask 值 。它会将文件的读和写权限分配给文件的属主,并将你设成文件的属主。一旦创建了文件,你就在脚本中有了完整的读写权限,但其他人没法访问它(当然,root 用户除外)。
1
tempfile=$(mktemp test.XXXXXX)
  • -t 选项会强制 mktemp 命令来在系统的临时目录来创建该文件。在用这个特性时,mktemp 命令会返回用来创建临时文件的全路径,而不是只有文件名
1
mktemp -t test.XXXXXX
1
mktemp
  • -d 选项告诉 mktemp 命令来创建一个临时目录而不是临时文件
1
tempdir=$(mktemp -d dir.XXXXXX)

记录消息

  • 将输出同时发送到显示器和日志文件,这种做法有时候能够派上用场。你不用将输出重定向两次,只要用特殊的 tee 命令就行。tee 命令相当于管道的一个 T 型接头。它将从 STDIN 过来的数据同时发往两处。一处是 STDOUT,另一处是 tee 命令行所指定的文件名:tee filename
1
date | tee testfile
  • 注意,默认情况下,tee 命令会在每次使用时覆盖输出文件内容。如果你想将数据追加到文件中,必须用-a 选项。

控制脚本

重温 Linux 信号

1
2
3
4
5
6
7
8
1 SIGHUP 挂起进程
2 SIGINT 终止进程
3 SIGQUIT 停止进程
9 SIGKILL 无条件终止进程
15 SIGTERM 尽可能终止进程
17 SIGSTOP 无条件停止进程,但不是终止进程
18 SIGTSTP 停止或暂停进程,但不终止进程
19 SIGCONT 继续运行停止的进程
  • Ctrl+C 组合键会生成 2 SIGINT 信号
  • Ctrl+Z 组合键会生成一个 18 SIGTSTP 信号,停止 shell 中运行的任何进程

捕获信号

  • 本例中用到的 trap 命令会在每次检测到 SIGINT 信号时显示一行简单的文本消息。捕获这些信号会阻止用户用 bash shell 组合键 Ctrl+C 来停止程序。

  • 每次使用 Ctrl+C 组合键,脚本都会执行 trap 命令中指定的 echo 语句,而不是处理该信号并允许 shell 停止该脚本。

  • 当脚本运行到正常的退出位置时,捕获就被触发了,shell 会执行在 trap 命令行指定的命令。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#!/bin/bash

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

echo This is a test script

count=1
while [ $count -le 10 ]
do
echo "Loop #$count"
sleep 1
count=$[ $count + 1 ]
done

echo "This is the end of the test script"
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
┌─(~/test)──────────────────────────────────────────────────────────(admin@miniarch:pts/1)─┐
└─(00:00:00 on master ✹)──> test.sh ──(一,1月01)─┘
This is a test script
Loop #1
Loop #2
^C Sorry! I have trapped Ctrl-C
Loop #3
Loop #4
^C Sorry! I have trapped Ctrl-C
Loop #5
Loop #6
Loop #7
^C Sorry! I have trapped Ctrl-C
Loop #8
Loop #9
Loop #10
This is the end of the test script

移除捕获

  • 删除已设置好的捕获。只需要在 trap 命令与希望恢复默认行为的信号列表之间加上两个破折号就行了。

  • 也可以在 trap 命令后使用单破折号来恢复信号的默认行为。单破折号和双破折号都可以正常发挥作用。

1
trap -- SIGINT

以后台模式运行脚本

  • 在用 ps 命令时,会看到运行在 Linux 系统上的一系列不同进程。显然,所有这些进程都不是运行在你的终端显示器上的。这样的现象被称为在后台(background)运行进程。在后台模式中,进程运行时不会和终端会话上的 STDIN、STDOUT 以及 STDERR 关联。

  • 以后台模式运行 shell 脚本非常简单。只要在命令后加个&符就行了。

  • 在终端会话中使用后台进程时一定要小心。注意,在 ps 命令的输出中,每一个后台进程都和终端会话(pts/0)终端联系在一起。如果终端会话退出,那么后台进程也会随之退出。

1
test.sh &

在非控制台下运行脚本

  • nohup 命令运行了另外一个命令来阻断所有发送给该进程的 SIGHUP 信号。这会在退出终端会话时阻止进程退出。
1
nohup test.sh &
  • 由于 nohup 命令会解除终端与进程的关联,进程也就不再同 STDOUT 和 STDERR 联系在一起。为了保存该命令产生的输出,nohup 命令会自动将 STDOUT 和 STDERR 的消息重定向到一个名为 nohup.out 的文件中。

  • 如果使用 nohup 运行了另一个命令,该命令的输出会被追加到已有的 nohup.out 文件中。当运行位于同一个目录中的多个命令时一定要当心,因为所有的输出都会被发送到同一个 nohup.out 文件中,结果会让人摸不清头脑。

作业控制

  • 启动、停止、终止以及恢复作业的这些功能统称为作业控制。通过作业控制,就能完全控制 shell 环境中所有进程的运行方式了。
1
jobs -l
  • 带加号的作业会被当做默认作业。在使用作业控制命令时,如果未在命令行指定任何作业号,该作业会被当成作业控制命令的操作对象。当前的默认作业完成处理后,带减号的作业成为下一个默认作业。任何时候都只有一个带加号的作业和一个带减号的作业,不管 shell 中有多少个正在运行的作业。

重启停止的作业

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
┌─(~)───────────────────────────────────────────────────────────────(admin@miniarch:pts/3)─┐
└─(13:45:11)──> sleep 9999 ──(一,1月01)─┘
^Z
[1] + 13136 suspended sleep 9999
┌─(~)───────────────────────────────────────────────────────────────(admin@miniarch:pts/3)─┐
└─(13:45:17)──> sleep 8888 & 148 ↵ ──(一,1月01)─┘
[2] 13155
┌─(~)───────────────────────────────────────────────────────────────(admin@miniarch:pts/3)─┐
└─(13:45:26)──> jobs -l ──(一,1月01)─┘
[1] + 13136 suspended sleep 9999
[2] - 13155 running sleep 8888
┌─(~)───────────────────────────────────────────────────────────────(admin@miniarch:pts/3)─┐
└─(13:45:35)──> fg ──(一,1月01)─┘
[1] - 13136 continued sleep 9999
^Z
[1] + 13136 suspended sleep 9999
┌─(~)───────────────────────────────────────────────────────────────(admin@miniarch:pts/3)─┐
└─(13:45:52)──> bg 148 ↵ ──(一,1月01)─┘
[1] - 13136 continued sleep 9999
┌─(~)───────────────────────────────────────────────────────────────(admin@miniarch:pts/3)─┐
└─(13:45:54)──> jobs -l ──(一,1月01)─┘
[1] - 13136 running sleep 9999
[2] + 13155 running sleep 8888

调整谦让度

  • 在多任务操作系统中(Linux 就是),内核负责将 CPU 时间分配给系统上运行的每个进程。调度优先级(scheduling priority)是内核分配给进程的 CPU 时间(相对于其他进程)。

  • 调度优先级是个整数值,从 -20(最高优先级)到+19(最低优先级)。默认情况下,bash shell 以优先级 0 来启动所有进程。

  • nice 命令允许你设置命令启动时的调度优先级。

1
nice -n 19 sleep 9999 &
  • 有时你想改变系统上已运行命令的优先级。这正是 renice 命令可以做到的。它允许你指定运行进程的 PID 来改变它的优先级。

  • 和 nice 命令一样,renice 命令也有一些限制:只能对属于你的进程执行 renice, 只能通过 renice 降低进程的优先级, root 用户可以通过 renice 来任意调整进程的优先级。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
┌─(~)───────────────────────────────────────────────────────────────(admin@miniarch:pts/3)─┐
└─(13:45:11)──> nice -0 sleep 9999 & ──(一,1月01)─┘
[1] 15885
┌─(~)───────────────────────────────────────────────────────────────(admin@miniarch:pts/3)─┐
└─(13:45:11)──> ps -p 15885 -o pid,ppid,ni,cmd ──(一,1月01)─┘
PID PPID NI CMD
15885 15500 5 sleep 9999
┌─(~)───────────────────────────────────────────────────────────────(admin@miniarch:pts/3)─┐
└─(13:45:11)──> renice -n 10 -p 15885 ──(一,1月01)─┘
15885 (process ID) 旧优先级为 -10,新优先级为 10
┌─(~)───────────────────────────────────────────────────────────────(admin@miniarch:pts/3)─┐
└─(13:45:11)──> ps -p 15885 -o pid,ppid,ni,cmd ──(一,1月01)─┘
PID PPID NI CMD
15885 15500 10 sleep 9999

函数

1
2
3
function name {
commands
}
1
2
3
name() {
commands
}
  • 如果在函数被定义前使用函数,你会收到一条错误消息。

  • 函数名必须是唯一的,如果你重定义了函数,新定义会覆盖原来函数的定义,这一切不会产生任何错误消息

函数返回值

  • bash shell 会把函数当作一个小型脚本,运行结束时会返回一个退出状态码。有 3 种不同的方法来为函数生成退出状态码。

  • 默认情况下,函数的退出状态码是函数中最后一条命令返回的退出状态码。在函数执行结束后,可以用标准变量$?来确定函数的退出状态码。

  • bash shell 使用 return 命令来退出函数并返回特定的退出状态码。return 命令允许指定一个整数值来定义函数的退出状态码

  • 记住,函数一结束就取返回值

  • 记住,退出状态码必须是 0~255

  • 如果在用$?变量提取函数返回值之前执行了其他命令,函数的返回值就会丢失。记住,$?变量会返回执行的最后一条命令的退出状态码。

  • 可以将函数的输出赋值给变量

  • read 命令输出了一条简短的消息来向用户询问输入值。bash shell 脚本非常聪明,并不将其作为 STDOUT 输出的一部分,并且忽略掉它。如果你用 echo 语句生成这条消息来向用户查询,那么它会与输出值一起被读进 shell 变量中。

1
2
3
4
5
6
7
8
9
10
11
12
13
#!/bin/bash

function dbl {
read -p "Enter a value: " value
echo $[ $value * 2 ]
return
}

echo $?
echo

result=$(dbl)
echo "The new value is $result"
1
2
3
4
5
6
┌─(~)───────────────────────────────────────────────────────────────(admin@miniarch:pts/3)─┐
└─(13:45:11)──> test.sh ──(一,1月01)─┘
0

Enter a value: 6
The new value is 12

向函数传递参数

  • bash shell 会将函数当作小型脚本来对待。这意味着你可以像普通脚本那样向函数传递参数

  • 函数名会在$0变量中定义,函数命令行上的任何参数都会通过$1、$2等定义

  • 也可以用特殊变量$#来判断传给函数的参数数目

  • 由于函数使用特殊参数环境变量作为自己的参数值,因此它无法直接获取脚本在命令行中的参数值

1
2
3
4
5
6
7
8
9
10
11
12
#!/bin/bash

function func {
echo $[ $1 * $2 ]
}
if [ $# -eq 2 ]
then
value=$(func $1 $2)
echo "The result is $value"
else
echo "Usage: test.sh a b"
fi
1
2
3
┌─(~)───────────────────────────────────────────────────────────────(admin@miniarch:pts/3)─┐
└─(13:45:11)──> test.sh 2 3 ──(一,1月01)─┘
The result is 6

在函数中处理变量

  • 默认情况下,你在脚本中定义的任何变量都是全局变量。在函数外定义的变量可在函数内正常访问。

  • 无需在函数中使用全局变量,函数内部使用的任何变量都可以被声明成局部变量。要实现这一点,只要在变量声明的前面加上 local 关键字就可以了

1
local temp
1
local temp=1
  • local 关键字保证了变量只局限在该函数中。如果脚本中在该函数之外有同样名字的变量,那么 shell 将会保持这两个变量的值是分离的。
1
2
3
4
5
6
7
8
9
10
11
#!/bin/bash

function func {
local temp=$[ $value + 5 ]
result=$[ $temp * 2 ]
}
temp=2
value=3
func
echo "The result is $result"
echo "temp is $temp"
1
2
3
4
┌─(~)───────────────────────────────────────────────────────────────(admin@miniarch:pts/3)─┐
└─(13:45:11)──> test.sh 2 3 ──(一,1月01)─┘
The result is 16
temp is 2

数组变量和函数

  • 试图将该数组变量作为函数参数,函数只会取数组变量的第一个值

  • 要解决这个问题,你必须将该数组变量的值分解成单个的值,然后将这些值作为函数参数使用。在函数内部,可以将所有的参数重新组合成一个新的变量

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#!/bin/bash

function fun {
local newarray
newarray=($(echo "$@"))
for (( i=0; i < $#; i++ )) {
newarray[$i]=$[ ${newarray[$i]} * 2 ]
}
echo ${newarray[*]}
}

myarray=(1 2 3 4 5)
echo "The original array is ${myarray[*]}"

# fun ${myarray[*]}

result=($(fun ${myarray[*]}))
echo ${result[*]}
1
2
3
4
┌─(~)───────────────────────────────────────────────────────────────(admin@miniarch:pts/3)─┐
└─(13:45:11)──> test.sh ──(一,1月01)─┘
The original array is 1 2 3 4 5
2 4 6 8 10

函数递归

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#!/bin/bash

function factorial {
if [ $1 -eq 1 ]
then
echo 1
else
local temp=$[ $1 - 1 ]
local result=`factorial $temp`
echo $[ $result * $1 ]
fi
}
read -p "Enter value: " value
result=$(factorial $value)
echo "The factorial of $value is: $result"
1
2
3
4
┌─(~)───────────────────────────────────────────────────────────────(admin@miniarch:pts/3)─┐
└─(13:45:11)──> test.sh ──(一,1月01)─┘
Enter value: 5
The factorial of 5 is: 120

创建库

  • 使用函数可以在脚本中省去一些输入工作,这一点是显而易见的。但如果你碰巧要在多个脚本中使用同一段代码呢?显然,为了使用一次而在每个脚本中都定义同样的函数太过麻烦。

  • 有个方法能解决这个问题!bash shell 允许创建函数库文件,然后在多个脚本中引用该库文件

  • 使用函数库的关键在于 source 命令。source 命令会在当前 shell 上下文中执行命令,而不是创建一个新 shell。可以用 source 命令来在 shell 脚本中运行库文件脚本。这样脚本就可以使用库中的函数了。

  • source 命令有个快捷的别名,称作点操作符(dot operator)。要在 shell 脚本中运行 myfuncs 库文件,只需添加下面这行:

1
. ./myfuncs

sed

1
echo "This is a test" | sed 's/test/big test/'
  • sed 编辑器并不会修改文本文件的数据。它只会将修改后的数据发送到 STDOUT。

  • 要在 sed 命令行上执行多个命令时,只要用-e 选项就可以了。

  • 两个命令都作用到文件中的每行数据上。命令之间必须用分号隔开,并且在命令末尾和分号之间不能有空格。

  • 替换命令在替换多行中的文本时能正常工作,但默认情况下它只替换每行中出现的第一处。要让替换命令能够替换一行中不同地方出现的文本必须使用替换标记(substitution flag)。替换标记会在替换命令字符串之后设置。

1
2
3
4
数字,表明新文本将替换第几处模式匹配的地方;
g,表明新文本将会替换所有匹配的文本;
p,表明原先行的内容要打印出来;
w file,将替换的结果写到文件中。
  • -n 选项将禁止 sed 编辑器输出。但 p 替换标记会输出修改过的行。将二者配合使用的效果就是只输出被替换命令修改过的行。

  • 由于正斜线通常用作字符串分隔符,因而如果它出现在了模式文本中的话,必须用反斜线来转义。这通常会带来一些困惑和错误。要解决这个问题,sed 编辑器允许选择其他字符来作为替换命令中的字符串分隔符:

  • 感叹号被用作字符串分隔符,这样路径名就更容易阅读和理解了

gawk

正则表达式