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 []

Shell test 命令 | runoob

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

Bash解析

Bash 命令行解析和 eval
Shell 环境和子 Shell

posted @ 2023-05-22 20:36  Mugetsukun  阅读(67)  评论(0编辑  收藏  举报