跳转至

shell 教程

1 基础

1.1 set 参数设置

  • set -u:如果有不存在的变量,打印错误信息,并退出
  • set -x:在执行命令前,打印执行的命令
  • set +x:取消 set -x 的效果
  • set -e:如果命令执行错误,打印错误信息,并退出(不适合管道)
  • set -o pipefail:对 set -e 的补充,管道命令执行错误,打印报错信息,并退出

以上命令可以整合一起写,如 set -uxe

1.2 exit 设置退出码

脚本默认退出码是最后一个 shell 指令执行结果,可以使用 exit 退出脚本并返回退出码。 - 示例

echo hello
exit 0

1.3 sh 参数

1.3.1 -c

  • 作用:sh -c 允许 Bash 将一个字符串作为完整的命令来执行。
  • 示例
sh -c 'ls -l; pwd'

1.3.2 -x

  • 作用:执行命令时打印执行的命令。一般用来调试脚本。
  • 示例
sh -x test.sh

2 变量和常量

命名规范:常量全大写,变量小写 - 常量:readonly+ 常量名 - 删除变量:unset+ 变量名(不能删除常量) - 读变量: - ${变量名} - $ 变量名 - 修改变量值:变量名=变量新值

#!/bin/bash
# 变量

str="hello world!"
echo ${str}

# 常量
readonly IVALUE=10
echo "常量"${IVALUE}

# 取消变量
unset str
echo ${str}

2.1 局部变量

  • 局部变量用 local 标识,一般用于函数
hello() {
  local str="hello"
  echo $str
}
hello
echo "\$str=$str"

2.2 变量替换

  • ${var:-word} 如果变量 var 为空或已被删除 (unset),那么返回 word,但不改变 var 的值。
  • ${var:=word} 如果变量 var 为空或已被删除 (unset),那么返回 word,并将 var 的值设置为 word。
  • ${var:?message} 如果变量 var 为空或已被删除 (unset),那么将消息 message 送到标准错误输出,可以用来检测变量 var 是否可以被正常赋值。若此替换出现在 Shell 脚本中,那么脚本将停止运行。
  • ${var:+word} 如果变量 var 被定义,那么返回 word,但不改变 var 的值。
$ echo ${val:-hello};echo $val
hello

$ echo ${val:=hello};echo $val
hello
hello
$ echo ${val:+world};echo $val
world
hello

2.3 特殊变量 ($$ $1 $* $@ $# $?)

#!/bin/bash
# 特殊变量
echo "\$0脚本名 "$0
echo "\$$脚本进程ID "$$
echo "\$n脚本第n个参数,\$1脚本第一个参数 "$1
echo "\$#脚本参数个数"$#
echo "\$*脚本所有参数 "$*
echo "\$@脚本所有参数 "$@
echo "\$?上一个脚本执行结果"$?

$* 被双引号包裹时,将是一个整体,这时与 $@ 有点不同

#!/bin/bash
# test.sh
for val in "$@"
do
  echo val
done
for val in "$*"
do
  echo val
done

执行脚本结果如下 $* 被当成一个整体被输出

$ ./test.sh 1 2 3 4 5
1
2
3
4
5
1 2 3 4 5

3 {} 的使用

3.1 {val1,val2,...,valn} 用法

  • 功能:展开集合中列出的元素
$ echo file{1,2,3}
file1 file2 file3

3.2 {start..end..step} 用法

  • 功能:展开集合范围中元素,并且可以指定步长
$ echo file{0..3}
file0 file1 file2 file3
$ echo {0..10..2}
0 2 4 6 8 10
  • 通常用于 for in 循环中
$ for i in {0..5};do echo file$i ;done
file0
file1
file2
file3
file4
file5

4 特殊符号使用

4.1 $()、`` 读取命令结果

  • $() 和`` 都是用来执行命令后提取执行结果到变量中。
  • 示例
$ a=`echo "hello"`
$ echo $a
hello
$ b=$(echo "hello")
$ echo $b
hello

4.2 ${ }

  • ${var} 可以获取变量的值,作用同 $var。此时 ${var} 相较于 $var 的好处时可以明确变量名 var 的界限
  • 示例:下面示例由于变量名被认为是 bb,导致解析结果错误
$ b="hello"
[here4@web17 bin]$ echo $bb

[here4@web17 bin]$ echo ${b}
hello
  • ${} 更高级的用法是用于字符串的操作,比如截取子串。${var:1:3}

4.3 $[] 和 $(())

  • $[]$(()) 都可以用来计算 +-*/%,但只能计算整数。一般推荐使用后者
  • 示例
$ echo $[1+4/2]
3
$ echo $((1+4/2))
3

5 运算符

5.1 算术运算符

  • 整形计算:利用 ((表达式)) 语法计算,或者使用 linux 上安装的可执行计算文件,如 expr
  • 浮点运算:使用 bc 计算
  • scale 指定精度,如 scale=6,代表保留小数点后 6 位
#!/bin/bash
#算术运算

##整形运算
a=$((1 + 2))
((b = 1 + 2))
echo "a=\$((1+2)),a=$a"
echo "((b=1+2))=$b"

##浮点型运算 使用echo+管道给bc传表达式
echo "scale=6;3.12*4.65/3" | bc
a=$(echo "scale=6;3.12*4.65/3" | bc)
echo $a

5.2 关系运算符

  • 使用 [][[]][] 即 test 指令的符号表示,[[]][] 的加强版)
  • 表达式与 [] 之间要空格隔开
  • 比较符如 -lt -ge 需要与比较数用空格隔开
  • [] 也可以测试文件属性,具体请看 test 文档
#!/bin/bash
#关系运算

##整形数值比较
a=1
b=3
if [ $a -lt $b ]; then
  echo "$a<$b=true"
else
  echo "$a<$b=false"
fi
##字符串比较
str1="hello"
str2="world"
if [ $str1 != $str2 ]; then
  echo "$str1!=$str2=true"
else
  echo "$str1!=$str2=false"
fi
## bool运算符-a且,-o或,!非
if [ $a -lt $b -a $str1 != $str2 ]; then
  echo "$a -lt $b -a $str1 != $str2 条件全真"
else
  echo "非全真"
fi

6 字符串

6.1 字符串 3 种表达形式

  • 变量名=str: 字符串不用引号包裹
  • 不被引号包围的字符串中出现变量时也会被解析,这一点和双引号 " " 包围的字符串一样。
  • 字符串中不能出现空格,否则空格后边的字符串会作为其他变量或者命令解析。
  • 变量名='str': 字符串用单引号包裹
  • 任何字符都会原样输出,在其中使用变量是无效的。
  • 字符串中不能出现单引号,即使对单引号进行转义也不行。
  • 变量名="str": 字符串用双引号包裹
  • 如果其中包含了某个变量,那么该变量会被解析(得到该变量的值),而不是原样输出。
  • 字符串中可以出现双引号,只要它被转义了就行。
  • 示例
# 脚本文件 str.sh
url=https://www.baidu.com/index.html
echo "url:$url"
echo 'url:$url'
echo url:$url
# 执行结果
$ sh str.sh
url:https://www.baidu.com/index.html
url:$url
url:https://www.baidu.com/index.html

https://blog.csdn.net/ITlinuxP/article/details/79060822

6.2 字符串长度

  • 语法:${#words}
  • 示例
$ echo $url
https://www.baidu.com/index.html
$ echo ${#url}
32

6.3 字符串拼接

语法形式:将字符串紧密相连(中间不留空格),或者用双引号包裹(可以用空格) - 示例: 下例中 p3 变量赋值中 str1 和 str2 中间有个空格,导致 str2 被解析成命令

$ cat -n str.sh 
     1  str1=hello
     2  str2=world
     3
     4  p1=$str1$str2
     5  p2="$str1 str2"
     6  p3=$str1 $str2
     7
     8  echo $p1
     9  echo $p2
    10  echo $p3
$ sh vim.txt 
vim.txt: line 6: world: command not found
helloworld
hello str2

6.4 指定位置和数量截取

  • 语法:0- 是前缀是指从右往左数位置
  • ${words:num_start:num_end}: 从第 num_start 个字符开始截取 num_end 个字符
  • ${words:num_start}: 从第 num_start 个字符开始截取到最后一个字符
  • ${words:0-num_start:num_end}: 从倒数第 num_start 个字符开始截取 num_end 个字符
  • ${words:0-num_start}: 从倒数第 num_start 个字符开始截取到最后一个字符
  • 示例
$ echo $url
https://www.baidu.com/index.html
$ echo ${url:8:3}
www
$ echo ${url:8}
www.baidu.com/index.html
$ echo ${url:0-8:3}
dex
$ echo ${url:0-8}
dex.html

6.5 匹配字符串截取

6.5.1 从左往右查找

注意# 表示从左往右模式,其中 * 表示通配符 - 语法: - ${str#substr}: 匹配以 substr 开头的字符串,再截取 substr 后到结尾的字符串 - ${str#*substr}: 匹配第一次出现 substr 的字符串,再截取 substr 后到结尾的字符串 - ${str##*substr}: 匹配最后一次出现 substr 的字符串,再截取 substr 后到结尾的字符串 - 示例

$ echo ${url}
https://www.baidu.com/index.html
$ echo ${url#https}
://www.baidu.com/index.html
$ echo ${url#*/}
/www.baidu.com/index.html
$ echo ${url##*/}
index.html

6.5.2 从右往左查找

注意% 表示从左往右模式,其中 * 表示通配符 - 功能:从 words1 中查找首次出现 words2 后的字符到结尾字符 - 示例


  • ${#str}${str:1:3} 分别求字符串长度和截取字符串子串
  • test 也可以操作字符串,功能更多
#!/bin/bash
#字符串操作

str="hello"
echo "${str}长度=${#str}"
echo "${str}从索引1开始3字符串=${str:1:3}"
echo "${str}从索引1开始子串=${str:1}"

7 数组

数组索引从 0 开始 - 数组赋值 - array=(val1 val2 ... valn) - array=([index1]=val1 [index2]=val2 [index3]=val3):index 可以不是连续的,比如:ages=([3]=24 [5]=19 [10]=12) - array[0]=val1;array[1]=val2;...;array[n-1]=valn - 注意事项:数组值之间用空格隔开 - 数组大小 - ${#array[@]} - ${#array[*]} - 数组全部数据 - ${array[@]} - ${array[*]} - 某一索引数据 - ${array[0]}

#!/bin/bash
set -xeu
# 数组

array1=(1 2 3 4 5)
echo ${#array1[@]}
echo "${array1[3]}"
echo "${array1[*]}"
echo "${array1[@]}"

array2=(
  1
  2
  3
  4
  5
)
array3[0]=1
array3[3]=4
array3[4]=5
echo "${#array3[*]}"
echo "${array3[0]}"
echo "${array3[1]}"

8 test 测试条件

  • 使用 man test 查看系统手册

8.1 (()) 提供高级算术运算

  • (()) 运算支持所有 c 语言定义的运算,且不要求空格分割
  • 语法形式:
  • ((expr)) :计算表达式内容
  • $((expr)) :取表达式计算结果
  • 示例
#!/bin/bash
a=$((b=1+2))
echo "a=$a b=$b"
c=$((10%3))
echo "c=$c"
d=$((a>c?1:0))
echo "d=$d"
  • 输出结果
a=3 b=3
a=4
c=1
d=1

8.2 [] 提供简单的数值比较、字符串比较、文件比较

  • 比较运算符
  • ( EXPRESSION ) : EXPRESSION is true
  • ! EXPRESSION : 非运算
  • EXPRESSION1 -a EXPRESSION2 : 且运算
  • EXPRESSION1 -o EXPRESSION2 : 或运算

8.2.1 字符比较

  • -n STRING : 字符长度非 0
  • STRING : 等价于 -n STRING
  • -z STRING : 字符长度等于 0
  • STRING1 = STRING2 : 等于
  • STRING1 != STRING2 : 不等于

8.2.2 数值比较(整数,非浮点数)

  • INTEGER1 -eq INTEGER2 : INTEGER1 等于 INTEGER2
  • INTEGER1 -ge INTEGER2 : INTEGER1 大于等于 INTEGER2
  • INTEGER1 -gt INTEGER2 : INTEGER1 大于 INTEGER2
  • INTEGER1 -le INTEGER2 : INTEGER1 小于等于 INTEGER2
  • INTEGER1 -lt INTEGER2 : INTEGER1 小于 INTEGER2
  • INTEGER1 -ne INTEGER2 : INTEGER1 不等于 INTEGER2

8.2.3 文件比较

  • FILE1 -nt FILE2 : FILE1 比 FILE2 新(mtime)
  • FILE1 -ot FILE2 : FILE1 比 FILE2 旧(mtime)
  • -d FILE : 文件存在且是目录
  • -e FILE : 文件存在
  • -f FILE : 文件存在且是一个常规文件
  • -s FILE : 文件存在且文件大小大于 0
  • -r FILE : 文件存在且具有可读权限
  • -w FILE : 文件存在且具有可写权限
  • -x FILE : 文件存在且具有可执行权限

8.3 [[]] 提供了针对字符串模式匹配功能

  • 双中括号是单中括号的扩展版,支持更多运算符
  • 比较运算符
  • && :
  • || :
  • != :
  • === :
  • < :
  • > :

9 条件语句

9.1 if 条件语句

  • if...then...elif...then...else...fi,其中 ifelif 语句块由 then 引导
#!/bin/bash

str="hello"
if [[ -z $str ]]; then
  echo "$str为空"
elif [[ $str = "hello" ]]; then
  echo "${str}=hello"
else
  echo "$str非空"
fi

9.2 case 条件语句

  • case...in...esac
  • caseesac 结尾(case 反序)
  • 每一个条件的语句块 ;; 结束
#!/bin/bash

printf "请输入数字1—4\n"
# -e 开启转义
echo -e '你输入的数字是:\c'
read -r NUMBER

case $NUMBER in
1)
  echo "你输入的数字是1"
  ;;
2)
  echo "你输入的数字是2"
  ;;
3)
  echo "你输入的数字是3"
  ;;
4)
  echo "你输入的数字是4"
  ;;
*)
  echo "请输入正确数字,你输入的是$NUMBER"
  ;;
esac

10 循环

  • break: 跳出整个循环体(参考 c,java 等语言)
  • continue: 跳出当前循环,继续下一循环(参考 c,java 等语言)

10.1 for 循环

  • for ((i = 0; i < 10; i++));do ... done
#!/bin/bash

array=(
  1
  2
  3
  4
  5
)
for ((i = 0; i < ${#array[*]}; i++)); do
  echo "${array[i]}"
done

10.2 for in 循环

  • for ... in ... do ... done
#!/bin/bash

array=(
  1
  2
  3
  4
  5
)
echo "循环数组"
for i in ${array[*]}; do
  echo "$i"
done
for i in 1 2 3 4 5; do
  echo "$i"
done
echo "打印当前路径下文件名"
i=0
for fl in ./*; do
  echo "文件$((++i))$fl"
done
if [[ $# -gt 0 ]]; then
  echo "你输入参数如下:"
  for i in "$@"; do
    echo "$i"
  done
else
  echo "你没有输入任意参数"
fi

10.3 while 循环

  • while command do ... done,与 c 中 while 一样,当条件假时退出循环
#!/bin/bash

echo -e "请输入任意整形值,输入0结束:\c"

while read -r NUM; do
  echo "你输入了$NUM"
  if [ $NUM -eq 0 ]; then
    echo "退出shell"
    #或者exit 0
    break
  fi
done

10.4 until 循环

  • until command do ... done,与 c 中 do while 一样,当条件真时退出循环
#!/bin/bash

a=0
b=4
until [ $((++a)) -gt $b ]; do
  echo "$a <= $b"
done

11 break 和 continue

  • breakcontinue 不同于 c 语言,可以指定跳出那一层循环,其中 continue n+1 等价于 break n,跳出第 n 层循环。(n 的含义是从当前循环层从里往外数,第几层,当前层为 1)

11.1 break

  • break n:跳出第 n 层循环,如空 3 层循环,2 表示跳出第 2 层循环,3 表示跳出最外层循环,1 表示跳出当前层循环,默认 n 就是 1
#!/bin/bash

#跳出循环break
for i in 0 1 2; do
  if [ $i -gt 1 ]; then
    break
  else
    echo $i
  fi
done
#跳出任意层循环 break n
for z in 0 1 2; do
  for i in 0 1 2; do
    for j in 0 1 2 3 4; do
      if [ $i -eq 1 ] && [ $j -eq 3 ]; then
        break 2
      else
        echo "$z $i $j"
      fi
    done
  done
done

11.2 continue

  • continue: 从此循环体中断,继续下一循环,类似 c
  • continue n: 继续到第 n 层循环,类似 break (n-1;n>1)
#!/bin/bash

#继续下一循环
for i in 0 1 2; do
  if [ $i -eq 1 ]; then
    continue
  else
    echo $i
  fi
done
#继续到任意层循环 continue n
for z in 0 1 2 3 4; do
  for i in 0 1 2 3 4; do
    for j in 0 1 2 3 4; do
      if [ $z -eq $i ] && [ $i -eq $j ]; then
        echo "$z $i $j全等继续最外层循环"
        continue 3
      else
        echo "$z $i $j"
      fi
    done
  done
done

12 printf

  • 语法:%[-wide.precision]转换字符
    • -:左对齐
    • wide:字符所占的总宽度,大于字符宽度用空格补齐;比如字符占 11 位,wide 为 15 位,则输入字符占 15 位
    • precision:在字符类型中表示能截取的最大字符数;在浮点类型数值中表示精度(小数点后多少位)
    • 转换字符:比如 d 表示整形,s 表示字符串,f 表示浮点型
  • 规则:必须以% 开头,尾部必须要转换字符(d、s、l 等)
$ printf "%-20.5s" "hello world"
hello               $ 
$ printf "%-20.5f" 123545.6568486849
123545.65685        $
conversion character argument type
di An integer, expressed as a decimal number.
o An integer, expressed as an unsigned octal number.
xX An integer, expressed as an unsigned hexadecimal number
u An integer, expressed as an unsigned decimal number.
c An integer, expressed as a character. The integer corresponds to the character's ASCII code.
s A string.
f A floating-point number, with a default precision of 6.
eE A floating-point number expressed in scientific notation, with a default precision of 6.
p A memory address pointer.
% No conversion; a literal percent sign ("%") is printed instead.

13 read

  • 功能:读取用户输入
  • 语法:read [-ers] [-a aname] [-d delim] [-i text] [-n nchars] [-N nchars] [-p prompt] [-t timeout] [-u fd] [name ...]
  • -a 后跟一个变量,该变量会被认为是个数组,然后给其赋值,默认是以空格为分割符。
  • -d 后面跟一个标志符,其实只有其后的第一个字符有用,作为结束的标志。
  • -p 后面跟提示信息,即在输入前打印提示信息。
  • -e 在输入的时候可以使用命令补全功能。(仅提示当前目录下已有的文件或目录名)
  • -n 后跟一个数字,定义输入文本的长度。(达到字符限制自动提交)
  • -r 屏蔽\,如果没有该选项,则\作为一个转义字符,有的话 \就是个正常的字符了。
  • -s 安静模式,在输入字符时不再屏幕上显示,例如 login 时输入密码。
  • -t 后面跟秒数,定义输入字符的等待时间。
  • -u 后面跟 fd,从文件描述符中读入,该文件描述符可以是 exec 新开启的
  • 示例
# 1........
echo "输入一个值"
read value
echo "你输入的是:$value"
# 输出结果如下.....
#输入一个值
#\123
#你输入的是:123

# 2........
echo "输入一个值"
read -r value
echo "你输入的是:$value"
# 输出结果如下.....
#输入一个值
#\123
#你输入的是:\123

# 3........
read -p "输入一个值:" value
echo "你输入的是:$value"
# 输出结果如下.....
#输入一个值:123 
#你输入的是:123

13.1 密码模式

  • 示例
#!/bin/bash

read  -s  -p "请输入您的密码:" pass
printf "\n您输入的密码是 %s \n" "$pass"
# 输出结果如下.....
#请输入您的密码:
#您输入的密码是 123456

13.2 读取文件

  • 示例
#!/bin/bash

count=1    # 赋值语句,不加空格
cat test.txt | while read line      # cat 命令的输出作为read命令的输入,read读到的值放在line中
do
   echo "Line $count:$line"
   count=$[ $count + 1 ]          # 注意中括号中的空格。
done
echo "finish"
exit 0

14 select

  • 功能:select 用来展示一系列选项供用户选择
  • 示例
#!/bin/bash
select i in mon tue wed exit
do
    case $i in
        mon) echo "Monday";;
        tue) echo "Tuesday";;
        wed) echo "Wednesday";;
        exit) exit;;
    esac
done
  • 结果
$ ./test.sh 
1) mon
2) tue
3) wed
4) exit
#? 1
Monday
#? 2
Tuesday
#? 3
Wednesday
#? 4

15 getopt

强烈建议使用getopt,而不是getops
getopt 是一个在 Unix/Linux 系统中用于 shell 脚本解析命令行参数的工具,它可以处理短选项(例如 -a-b)和长选项(例如 --long-option)。以下是一个简单的示例:

#!/bin/bash

# 定义选项及其含义
OPTIONS=":hvd:"
LONG_OPTIONS="help,version,debug"

# - 使用 getopt 处理命令行参数
ARGS=$(getopt --options=$OPTIONS --longoptions=$LONG_OPTIONS --name "$0" -- "$@")

if [ $? -ne 0 ]; then
    # 参数解析出错时输出帮助信息并退出
    echo "Usage: $0 [-h|--help] [-v|--version] [-d|--debug]"
    exit 1
fi

eval set -- "$ARGS"

while true; do
    case "$1" in
        -h|--help)
            echo "Help message goes here"
            exit 0
            ;;
        -v|--version)
            echo "Version info goes here"
            exit 0
            ;;
        -d|--debug)
            DEBUG=true
            shift
            ;;
        --)
            # "--" 表示选项结束,之后的参数为非选项参数
            shift
            break
            ;;
        *)
            echo "Invalid option: -$OPTARG"
            exit 1
            ;;
    esac
done

echo "Non-option arguments: $@"

在这个脚本中:

  • OPTIONS=":hvd:" 定义了三个短选项 -h-v-d,冒号表示该选项后面需要跟一个参数。
  • LONG_OPTIONS="help,version,debug" 定义了对应的长选项。
  • getopt 命令会将解析后的结果赋值给 ARGS 变量。
  • 使用 eval set -- "$ARGS" 将解析后的参数重新排列以便于循环处理。
  • case 语句中根据不同的选项执行相应的操作。

注意:不同系统中的 getopt 实现可能有所不同,上述示例适用于 GNU 版本的 getopt。对于不支持长选项的系统,可能需要使用其他方法或库来实现。

16 getopts

  • 功能:解析传入 shell 脚本的参数
  • 语法:getopts optstring name
    • optstring: 可选字符,格式有 2 种,一种只含选项;另一种选项 + 值,这种需要在可选参数后加上冒号 :
      • "v":解析参数 -v
      • "v:":解析参数 -v value,选项后接值数据。这个 value 值会被放入 OTPARG 变量中
    • name: 当解析到了 optstring 指定的选项后,会将选项值放入 name 变量中,name 变量名称任取;当解析失败时,会将问号 ? 放入 name 变量中。

16.1 变量 OPTARG、OPTIND

- `OPTIND`表示的是下一个选项参数的位置,不是当前选项参数的位置。
- `getops`会根据`OPTIND`来解析哪一个位置的参数,因此,不要去主动修改`OPTIND`的值。当然,如果想要在多个地方调用getops时,可以主动修改OPTIND变量值。
- `OPTARG`只在解析参数选项时,提取值到另一个变量中保存。解析完后这个OPTARG变量数据会清空
- `OPTIND`会保留最后一个参数索引,整个shell生命周期都存在
  • OPTARG: 解析带选项值的参数时,选项后面的值放入这个变量中
  • OPTIND: 表示下一个选项参数的索引。(默认值是 1,当选项带值的,也是显现选项所在的位置)
  • 示例
$ cat test.sh 
echo "start:$OPTIND"
while getopts "a:bc" opt
do
        case $opt in
                a)
                echo "a 选项:$OPTIND $OPTARG"
                ;;
                b)
                echo "b 选项:$OPTIND $OPTARG"
                ;;
                c)
                echo "c 选项:$OPTIND $OPTARG"
                ;;
                ?)
                echo "参数解析失败"
                ;;
        esac
done
echo "end:$OPTIND $OPTARG"
$ sh test.sh -c -a hello
start:1
c 选项:2 
a 选项:4 hello
end:4 
$ sh test.sh -g
11.sh: illegal option -- g
参数解析失败

17 shift

  • 功能:将 shell 脚本参数向左移动多少位,左边的参数会被移除
  • 语法:shift [n]
  • 示例
$ cat test.sh
echo "start:参数总数$#"
shift 2
echo "shift:参数总数$#"
for arg in "$@"
do 
        echo $arg
done
$ sh test.sh 1 2 3 4
start:参数总数4
shift:参数总数2
3
4

18 函数

有些发行版,比如Ubuntu,sh默认指向的是dash,而不是bash,因此用function会报错,报错如`Syntax error:"("unexpected`,这时需要使用bash而不是sh执行。
- 函数 () 中不能有形参,但函数内部可通过 $1 $2 ... $9 ${10} ... ${n} 获取参数,当参数多余 10 个时,参数要由 ${n} 而不是 $n 获取 - 调用函数不需要带 (),传参接着函数名后写,如:fun_name 1 2 "3",表示调用函数 fun_name 并且传给函数 3 个参数 - 函数中也能使用 return,但只能返回整形数值,如:return 2,能不能 return "2" - function 标识可有可无,如 function add(){} 等价于 add(){}

#!/bin/bash

add() {
  #计算参数总和
  all=0
  for i in "$@"; do
    all=$((all + i))
  done
  return $all
}
add 1 2 3 4
#$?获取上一指令结果,此处为add函数返回值
echo "函数add 1 2 3 4返回值:$?"

19 shopt

shopt 是一个用于管理 Bash shell 中的选项设置的命令。以下是一些重要的参数及其解释: 1. -s :设置选项。使用这个参数可以打开指定的选项。 2. -u :取消设置选项。使用这个参数可以关闭指定的选项。 3. -q :静默模式。在查询选项状态时,如果选项被打开则不显示任何输出,关闭则显示一个错误消息。 4. -o :列出所有设置的选项。 5. -p :列出所有可以设置的选项,但不显示其当前状态。 6. -s-u 后跟选项名称:使用这些参数可以设置或取消特定的选项。

一些常用的选项包括: - histappend :如果设置了该选项,则在退出 shell 时追加历史记录,而不是覆盖之前的历史记录。 - nocaseglob :如果设置了该选项,则在文件名匹配时不区分大小写。 - nullglob :如果设置了该选项,则在没有匹配的文件时,通配符模式将被删除而不是保留原样。 - globstar :如果设置了该选项,则可以使用双星号 ** 进行递归匹配文件和目录。 - errexit :如果设置了该选项,则脚本中的任何命令返回非零退出状态时,脚本会立即退出。 - nounset :如果设置了该选项,则尝试使用未设置的变量会导致脚本退出。

这只是一些常用选项的例子,shopt 命令还有许多其他选项可以使用。你可以使用 shopt -p 来查看当前设置的选项,或者使用 shopt -o 来查看所有可用的选项。

20 扩展进阶

20.1 包含其它 shell 文件

包含文件类似于 c 语言中的 include,可以将外部脚本合并到当前脚本文件中 - 方式一:. file.sh,其中 . 与文件名间有一空格 - 方式二:source file.sh

#!/bin/bash

. ./str.sh
# 使用str.sh中定义的str
echo "$str"
source ./function.sh

20.2 制作并使用 shell 函数库

  1. 将 shell 函数写入 shell 文件中
  2. 在另一个 shell 文件中或命令行中使用 source. 将步骤 1 中的 shell 函数文件包含进来
  3. 在 shell 文件中或命令行中使用函数

20.3 交互式输入的自动化

  • 使用场景:在自动化安装(一键安装)中,不能使用交互式来读取输入值,这时可以使用以下方式来处理

20.3.1 使用 echo+ 管道方式

  • 示例
$ cat test.sh
echo "开始交互式输入"
echo "选择1:Y/N:"
read opt1
echo "选择2:Y/N:"
read opt2
echo "选择3:Y/N:"
read opt3
echo "opt1:$opt1 opt2:$opt2 opt3:$opt3"
$ echo -e "Y\nY\nN"|sh 11.sh
选择1:Y/N:
选择2:Y/N:
选择3:Y/N:
opt1:Y opt2:Y opt3:N

20.3.2 使用文件重定向方式

  • 示例
$ cat opt.txt
Y
N
Y
$ sh 11.sh <opt.txt
开始交互式输入
选择1:Y/N:
选择2:Y/N:
选择3:Y/N:
opt1:Y opt2:N opt3:Y

20.4 脚本执行方式与是否产生子 shell 的关系

`source file`取脚本里面的语句依次在当前shell里面执行,没有建立新的子shell
  • 参考博客
  • $(commond)bash shellfile./filename:会产生新的子 shell,子 shell 继承父 shell 的一切环境变量、已打开的文件标识符、当前工作目录等。子 shell 的修改不影响父 shell
  • source fileexec file :不会产生子 shell,source 会加 shell 环境加载到父 shell 中,而 exec 会执行子 shell,不再执行 exec 命令后的内容,相当于原父 shell 退出,子 shell 继承父 shell 的 pid,执行子 shell。

20.5 子 shell 导致局部变量

  • 参考
  • 方法一:shopt -s lastpipe 在 bash4.2+ 版本上支持
#!/usr/bin/env bash
shopt -s lastpipe
n=0
printf "%s\n" {1..10} | while read i; do (( n+=i )); done
echo $n
  • 方法二:使用重定向 +EOF 技术
n=0
SUMMANDS="$(printf '%s\n%s\n%s\n%s\n%s\n%s\n%s\n%s\n%s\n%s\n' 1 2 3 4 5 6 7 8 9 10)"
while read i; do n=$(( n + i )); done <<SUMMANDS_INPUT
$SUMMANDS
SUMMANDS_INPUT
echo $n
  • 示例 2
[root@centos7 ~]# while read v1 v2;do echo "$v1 $v2";done<<end
> 1
> 2
> end
1 
2 

21 nohup 重定向日志清空问题

  • 现象:当使用 nohup ./test.sh > ./test.log 2>&1 & ,会生成 test.log 文件,然后我们在脚本文件 test.sh 中执行清空文件指令,如 cp /dev/null ./test.log> ./test.log 时,发现 test.log 文件大小依然没有降低,但 test.log 文件中只有新增的日志。
  • 解决:使用追加方式写入到日志文件中,如:nohup ./test.sh >> ./test.log 2>&1 &