Shell手册-Bash
资料来源
Shell全面掌握教程 | 骏马金龙
Shell 教程 | runoob
Bash 脚本教程 | 网道
基础
Bash 中基本数据类型只有字符串类型
echo 123
Bash 中字符串的串联操作,直接将两段数据连接在一起即可,不需要任何操作符。
echo "123""456"
echo 123 456
变量
a=1
echo $a
a="www.cnblogs.com/mugetsukun"
echo $a
echo ${#a} #获取变量a保存的字符长度
a='123456'
echo $a
echo $asd #未定义变量
a=
echo $a #空变量
a="123"
echo $a 456 #变量替换,等同于echo 123 456
echo `id root`
echo $(id root)
$ id root
uid=0(root) gid=0(root) groups=0(root)
echo uid=0(root) gid=0(root) groups=0(root) # 命令替换,先执行 cmd,将 cmd 的输出结果替换到 $() 或反引号位置处
>(command)
<(command)
#进程替换,表示将 `command` 命令的输出作为一个临时文件来处理,这个临时文件可以像普通文件一样进行读写操作,但是它并没有被保存到磁盘上,而是被处理器暂存器或内存中。
$ echo <(echo cmd)
/dev/fd/63
a=123
echo $[a+3] # echo $[$a+3]
echo $((a+3)) # echo $(($a+3))
echo 'PATH=/usr/local/mysql/bin:$PATH' >/etc/profile.d/mysql.sh # 环境变量
source /etc/profile.d/mysql.sh
exit n # 0 正常 1 错误
shift n # 删除第n个参数
break n # 退出整个循环
continue n # 退出当前循环进入下一次循环。n表示继续执行向外退出n层的循环。
return n # 退出整个函数。n表示函数的退出状态码。
echo $[ (x+y)*2 ] # 数值运算
echo 1.2 2.3 | awk '{print $1+$2}' #小数运算
set shopt
#!/bin/bash
set -e # 遇到错误立即退出脚本
set -x # 输出命令及其参数
set -u # 遇到未定义变量立即退出脚本
set -v # 输出命令,不过不输出扩展后的值
set -n # 检查脚本语法错误
set -o nounset # 遇到未定义变量时退出脚本
set -p # 打印当前 shell 的所有变量和函数定义
#!/bin/bash
shopt -s cdable_vars # 启用 cdable_vars 选项
shopt -s extglob # 启用 extglob 选项
shopt -u nocaseglob # 禁用 nocaseglob 选项
shopt -q globstar # 查询 globstar 选项是否已开启
特殊变量
$n:脚本的位置参数
$0:shell或shell脚本的名称
$*:当前脚本的所有参数(不包括程序本身);
$@:扩展为位置参数,"$@"会将每个位置参数单独引起来,"$@"等价于"$1" "$2" "$3"...
$#:位置参数的个数
$$:当前Shell的进程PID,在某些子Shell(如小括号()开启的子Shell)下,会被继承。如果可以,建议使用$BASHPID替代$$
$?:最近一个前台命令的退出状态码
$!:最近一个后台命令的进程PID
$-:当前Shell环境的一些特殊设置,比如是否交互式
$_:最近一个前台命令的最后一个参数(还有其它情况,该变量用的不多,所以不追究了)
数组
arr[0] # 访问第1个元素
arr[-1] # 访问倒数第1个元素
arr['name'] # 访问key=name里的元素
arr[1]=one # 赋值后直接创建数组
echo ${arr[1]}
one
echo ${arr[*]} # 访问数组所有元素的值
echo ${!arr[*]} # 访问数组所有索引
echo ${#arr[*]} # 访问数组所有非空元素个数
$ arr+=("well")
declare -p arr
declare -a arr=([0]="well") # 可以用+=操作符赋值数组
unset arr[1] # 删除第1个元素
# for循环遍历获取每个元素的值
for i in ${arr[@]};do
echo $i
done
# 获取每个元素的索引,间接的可以获取每个元素的值
for i in ${!arr[@]};do
echo index:$i
echo value: ${arr[$i]}
done
#- `i in ${!arr[@]}`:循环的条件表达式,其中 `${!arr[@]}` 表示数组 `arr` 中所有下标的列表。
#- `${arr[$i]}`:表示数组 `arr` 中下标为 `i` 的元素的值。
#- `echo index:$i`:输出数组元素的下标。
#- `echo value: ${arr[$i]}`:输出数组元素的值。
# 用数组长度的方式遍历
for((i=0;i<${#arr[@]};i++)){
echo $i
echo ${arr[$i]}
}
#- `((i=0;i<${#arr[@]};i++))`:循环的条件表达式,其中 `${#arr[@]}` 表示数组 `arr` 的长度。
# - `i=0`:将计数器 `i` 的初始值设为 0。
# - `i<${#arr[@]}`:循环条件,当 `i` 小于数组 `arr` 的长度时循环继续执行。
# - `i++`:每次循环结束后将计数器 `i` 的值增加 1。
`declare` 命令用于声明变量,并且可以设置变量的属性。`declare` 命令也可以用来定义函数和设置函数属性。
语法:
declare [-aAfFilnrtux] [-p] [NAME[=VALUE] ...]
常用选项:
- `-a`:将变量定义为数组。
- `-A`:将变量定义为关联数组。
- `-f`:将 NAME 定义为函数名称。
- `-i`:将变量定义为整型变量。
- `-l`:将变量的值转换为小写字母。
- `-n`:将 NAME 定义为引用另一个变量的名称。
- `-r`:将变量定义为只读变量。
- `-t`:将变量定义为 "trace" 变量,用于调试脚本。
- `-u`:将变量的值转换为大写字母。
- `-x`:将变量导出到子进程的环境中。
- `-p`:显示所有已设置的变量及其属性。
declare my_var="hello" # 声明一个普通变量
declare -i my_num=10 # 声明一个整型变量
declare -a my_arr=(1 2 3) # 声明一个数组变量
declare -r my_const="world" # 声明一个只读变量
declare -x PATH="/usr/local/bin:$PATH" # 声明一个导出到子进程的环境变量
declare -f my_func # 声明一个函数名称
declare -p # 显示所有已设置的变量及其属性
命令组合
command1 ; command2 # 先执行command1,后执行command2
command1 && command2 # 如果command1能执行,就执行command2
command1 || command2 # 如果command1不能执行,就执行command2
(command1;command2)
{command1;command2;command3; }
{
command1
command2
command3
}
基本重定向
# 文件描述符 file descriptor,fd
fd=0,/dev/stdin -> /proc/self/fd/0 # 0:标准输入
fd=1,/dev/stdout -> /proc/self/fd/1 # 1:标准输出
fd=2,/dev/stderr ->/proc/self/fd/2 # 2:标准错误
>:覆盖输出
>>:追加输出
<:输入
<<:文档输入
<<<:字符串输入
&>:将标准错误和标准输出覆盖到文件
&>>:将标准错误和标准输出追加到文件
cat file # 默认从终端中读取数据
cat < file # 改为从file读取数据
EOF:end of file
EOL:end of line
EOB:end ofi block
tee
tee
命令用于从标准输入读取数据,并将其复制到标准输出和一个或多个文件中。
$ cat data.txt | tee -a file.txt
这个命令将 data.txt
文件中的内容读取到标准输入中,然后 tee
命令将其复制到标准输出和 file.txt
文件中,并将数据追加到 file.txt
文件的末尾。
test []
sh_file=test.sh
[ -x "$sh_file" ] && ./$sh_file || { echo "不可执行,退出";exit 1; }
test -x "$sh_file" && ./$sh_file || { echo "不可执行,退出";exit 1; }
测试符
文件类
- -e 文件是否存在 (exist)
- -f 文件是否存在且为普通文件 (file)
- -d 文件是否存在且为目录 (directory)
- -b 文件是否存在且为块设备 block device
- -c 文件是否存在且为字符设备 character device
- -S 文件是否存在且为套接字文件 Socket
- -p 文件是否存在且为命名管道文件 FIFO (pipe)
- -L 文件是否存在且是一个链接文件 (Link)
文件属性类
- -r 文件是否存在且当前用户可读
- -w 文件是否存在且当前用户可写
- -x 文件是否存在且当前用户可执行
- -s 文件是否存在且大小大于 0 字节,即检测文件是否非空文件
- -N 文件是否存在,且自上次 read 后是否被 modify
文件比较
- -nt (newer than) 判断 file1 是否比 file2 新
- -ot (older than) 判断 file1 是否比 file2 旧
- -ef (equal file) 判断 file1 与 file2 是否为同一文件
数值大小
- -eq 两数值相等 (equal)
- -ne 两数值不等 (not equal)
- -gt n1 大于 n2 (greater than)
- -lt n1 小于 n2 (less than)
- -ge n1 大于等于 n2 (greater than or equal)
- -le n1 小于等于 n2 (less than or equal)
字符串比较
- -z str (zero) 判定字符串是否为空?str 为空串,则 true
- str
- -n str 判定字符串是否非空?str 为串,则 false。注:-n 可省略
- str1 = str2
- str1 == str2 str1 和 str2 是否相同,相同则返回 true。”==” 和”=” 等价
- str1 != str2 str1 是否不等于 str2,若不等,则返回 true
- str1 > str2 str1 字母顺序是否大于 str2,若大于则返回 true
- str1 < str2 str1 字母顺序是否小于 str2,若小于则返回 true
逻辑运算
-
-a 或 && (and)
两表达式同时为 true 时才为 true。
“-a” 只能在 test 或 [] 中使用,&& 只能在 [[]] 中使用 -
-o 或 || (or)
两表达式任何一个 true 则为 true。
“-o” 只能在 test 或 [] 中使用,|| 只能在 [[]] 中使用 -
!
对表达式取反 -
( )
改变表达式的优先级,为了防止被 shell 解析,应加上反斜线转义 ( )
if语句
if condition1;then
command1;
[elif condition2;then
command2;]
[else commandN;]
fi
if condition1
then
command1
elif condition2
then
command2
else
commandN
fi
if [ ! -d ~/.ssh ];then
mkdir ~/.ssh
chown -R $USER.$USER ~/.ssh
chmod 700 ~/.ssh
fi
if
:条件语句,如果条件为真则执行then
后面的命令。[ ! -d ~/.ssh ]
:检查当前用户的 home 目录下是否存在.ssh
目录。其中,[ ... ]
表示条件测试,!
表示逻辑非运算符,-d
表示检查目录是否存在,~/.ssh
表示要检查的目录路径。then
:如果条件测试为真,则执行下面的命令。mkdir ~/.ssh
:创建.ssh
目录。其中,mkdir
是创建目录的命令,~/.ssh
表示要创建的目录路径。chown -R $USER.$USER ~/.ssh
:设置目录.ssh
的拥有者和所属组为当前用户。其中,chown
命令用于设置文件或目录的拥有者和所属组,-R
表示递归设置,$USER.$USER
表示当前用户的用户名和所属组名,~/.ssh
表示要设置的目录路径。chmod 700 ~/.ssh
:设置目录.ssh
的权限为 700(rwx------),即只有当前用户具有读写执行权限,其他用户没有任何权限。其中,chmod
命令用于设置文件或目录的权限,700
表示要设置的权限值,~/.ssh
表示要设置的目录路径。fi
:条件语句结束标记,表示条件语句块的结束。
if grep 'test1' /etc/passwd &>/dev/null;then
echo '用户"test1"存在'
elif grep 'test2' /etc/passwd &>/dev/null;then
echo '用户"test2"存在'
else
echo '没有该用户,退出进程'
exit 1
fi
if
:条件语句,如果条件为真则执行then
后面的命令。grep 'test1' /etc/passwd &>/dev/null
:在文件/etc/passwd
中查找字符串test1
。其中,grep
命令用于在文件中查找指定字符串,/etc/passwd
表示要查找的文件路径,&>/dev/null
表示将命令的标准输出和标准错误输出都重定向到/dev/null
,即不显示任何输出信息。then
:如果条件测试为真,则执行下面的命令。echo '用户"test1"存在'
:输出提示信息,表示用户test1
已经存在。elif grep 'test2' /etc/passwd &>/dev/null
:如果第一个条件测试为假,则执行第二个条件测试。在文件/etc/passwd
中查找字符串test2
。其中,elif
表示如果第一个条件测试为假,则执行这个条件测试。echo '用户"test2"存在'
:输出提示信息,表示用户test2
已经存在。else
:如果两个条件测试都为假,则执行下面的命令。echo '没有该用户,退出进程'
:输出错误信息,表示应该创建用户。exit 1
:退出脚本,并将退出状态码设为 1。其中,exit
命令用于退出脚本,1
表示退出状态码。状态码为 0 表示命令执行成功,非 0 表示命令执行失败。
case
case 值 in
模式1)
command1
command2
...
commandN
;;
模式2)
command1
command2
...
commandN
;;
esac
echo '输入 1 到 4 之间的数字:'
echo '你输入的数字为:'
read aNum
case $aNum in
1) echo '你选择了 1'
;;
2) echo '你选择了 2'
;;
3) echo '你选择了 3'
;;
4) echo '你选择了 4'
;;
*) echo '你没有输入 1 到 4 之间的数字'
;;
esac
for循环
for var in item1 item2 ... itemN
do
command1
command2
...
commandN
done
for var in item1 item2 ... itemN; do command1; command2… done;
for loop in 1 2 3 4 5
do
echo "The value is: $loop"
done
while循环
while condition
do
command
done
#!/bin/bash
int=1
while(( $int<=5 ))
do
echo $int
let "int++"
done
函数
function func_name() {CMD_LIST}
local #可以用在函数内部表示定义一个局部变量,局部变量在函数执行完毕后就消失,不会影响函数外部的环境。
return #定义函数的返回值,每当执行到函数内的 return 时,函数就会终止执行,直接退出函数。在 Shell 中,函数的返回值其实就是退出状态码。
export #用于设置环境变量。环境变量是在操作系统中用来指定各种参数或者配置信息的变量,可以被 shell 和子进程继承和使用。
#常用选项:
#- `-f`:显示所有的 shell 函数。
#- `-n`:取消已设置的变量。
#- `-p`:显示所有已设置的变量。
export PATH=$PATH:/usr/local/bin # 将 /usr/local/bin 目录添加到 PATH 环境变量中
export LANG=en_US.UTF-8 # 设置语言环境为英文 UTF-8
export MY_VAR="Hello world" # 设置一个自定义的环境变量 MY_VAR
export -n MY_VAR # 取消 MY_VAR 环境变量的设置
export -p # 显示所有已设置的环境变量
临时文件
直接创建临时文件,尤其在/tmp目录里面,往往会导致安全问题。
首先,/tmp目录是所有人可读写的,任何用户都可以往该目录里面写文件。创建的临时文件也是所有人可读的。
$ touch /tmp/info.txt
$ ls -l /tmp/info.txt
-rw-r--r-- 1 ruanyf ruanyf 0 12月 28 17:12 /tmp/info.txt
上面命令在/tmp目录直接创建文件,该文件默认是所有人可读的。
其次,如果攻击者知道临时文件的文件名,他可以创建符号链接,链接到临时文件,可能导致系统运行异常。攻击者也可能向脚本提供一些恶意数据。因此,临时文件最好使用不可预测、每次都不一样的文件名,防止被利用。
最后,临时文件使用完毕,应该删除。但是,脚本意外退出时,往往会忽略清理临时文件。
生成临时文件应该遵循下面的规则。
- 创建前检查文件是否已经存在。
- 确保临时文件已成功创建。
- 临时文件必须有权限的限制。
- 临时文件要使用不可预测的文件名。
- 脚本退出时,要删除临时文件(使用trap命令)。
mktemp
$ mktemp
/tmp/tmp.4GcsWSG4vj
$ ls -l /tmp/tmp.4GcsWSG4vj
-rw------- 1 ruanyf ruanyf 0 12月 28 12:49 /tmp/tmp.4GcsWSG4vj
trap
`trap` 命令用于在 shell 脚本中捕捉和处理信号。信号是在 Linux 中用于处理进程之间通信和控制的一种机制,例如 `Ctrl+C` 产生的中断信号 `SIGINT`。
语法:
trap command signal
参数:
- `command`:指定在捕捉到信号后要执行的命令或脚本。
- `signal`:要捕捉的信号名称或编号,例如 `SIGINT`、`SIGHUP` 等。如果省略 `signal` 参数,则表示捕捉所有信号。
示例:
下面示例代码中定义了一个处理 `Ctrl+C` 中断信号的函数 `ctrl_c_handler`,并使用 `trap` 命令将其与 `SIGINT` 信号绑定,当接收到 `Ctrl+C` 中断信号时,会执行该函数。
```bash
#!/bin/bash
ctrl_c_handler() {
echo "Ctrl+C interrupt signal received, terminating the program..."
exit 1
}
trap ctrl_c_handler SIGINT
echo "Press Ctrl+C to interrupt the program..."
while true; do
sleep 1
done