Learn Bash实践

Learn Bash实践

本教程根据 Bash脚本教程, 经过自身实践, 而得. 作为笔记性质存在.

https://wangdoc.com/bash/

2. 基本语法

  1. echo -n(取消换行) -e(解释转义字符)

  2. 快捷键:

  • ctrl + L : clear
  • ctrl + U: 从光标位置删除到行首
  • ctrl + PgUp: 切换上一个Bash

3. 模式扩展

Shell 先扩展命令, 后执行.

  1. **~ **: 用户目录
  2. ? : 任意单个字符
  3. * : 任意个任意字符
  4. **[…] **: (方括号扩展) 匹配方括号内的 任 一个 字符
  5. [!..][^…] : 非方括号内的字符

举例: [!a-zA-Z] : 非 a-z, A-Z 的字符

  1. {…} : (大括号扩展) 扩展为大括号内的所有值

大括号扩展不是文件扩展, 会强行扩展为所有. 一般在创建文件时使用.

  1. {start…end} : 扩展为连续序列

{start…end…step} : 指定步长

  1. $xxx : 变量扩展 ${变量名}

${!string*}${!string@}返回所有匹配给定字符串string的变量名。

  1. $(…) : 子命令扩展, 返回 子命令的执行结果

老式语法: echo `date`
Tue Jan 28 00:01:13 CST 2020

  1. $((…)) : 算数扩展 echo $((2 + 2))

  2. [[:class:]] : 字符类 (文件匹配)

  • [[:alnum:]]:匹配任意英文字母与数字
  • [[:alpha:]]:匹配任意英文字母
  • [[:blank:]]:空格和 Tab 键。
  • [[:cntrl:]]:ASCII 码 0-31 的不可打印字符。
  • [[:digit:]]:匹配任意数字 0-9。
  • [[:graph:]]:A-Z、a-z、0-9 和标点符号。
  • [[:lower:]]:匹配任意小写字母 a-z。
  • [[:print:]]:ASCII 码 32-127 的可打印字符。
  • [[:punct:]]:标点符号(除了 A-Z、a-z、0-9 的可打印字符)。
  • [[:space:]]:空格、Tab、LF(10)、VT(11)、FF(12)、CR(13)。
  • [[:upper:]]:匹配任意大写字母 A-Z。
  • [[:xdigit:]]:16进制字符(A-F、a-f、0-9)。

举例: echo [[:lower:]]* 匹配小写字母开头的所有文件

  • [![:digit:]](或[^[:digit:]]) 匹配所有非数字。
  1. 量词语法

量词是正则表达式中的一种元字符,用于指定匹配模式的重复次数。

  • ?(pattern-list):模式匹配零次或一次。
  • *(pattern-list):模式匹配零次或多次。
  • +(pattern-list):模式匹配一次或多次。
  • @(pattern-list):只匹配一次模式。
  • !(pattern-list):匹配给定模式以外的任何内容。

举例:

$ ls abc?(.)txt
abctxt abc.txt

$ ls abc+(.txt)
abc.txt abc.txt.txt
  1. shopt命令可以调整 Bash 的行为。它有好几个参数跟通配符扩展有关。

参考: https://wangdoc.com/bash/expansion

4. 引号和转义

  1. **\ **: 用于转义
  2. **‘xxx’ 单引号 **: 特殊字符(*, $, \) 在单引号内都变成普通字符
  3. **“xxx” 双引号 **: 部分特殊字符($, `, \) 会被自动扩展. ( 变量, 子命令扩展, 转义)
  4. Here 文档 : 处理多行文本的概念.
<<[DELIMITER]
Your text here
[DELIMITER]
  1. Here 字符串: 处理 多行字符串
$ md5sum <<< 'ddd'
# 等同于
$ echo 'ddd' | md5sum

5. 变量

1. 环境变量

Bash 环境自带的变量, env可以展示

2. 创建变量

如果变量的值包含空格,则必须将值放在引号中。(Bash所有变量都是字符串)

myvar="hello world"
e=$(ls -l foo.txt)      # 变量值可以是命令的执行结果
f=$((5 * 7))            # 变量值可以是数学运算的结果
3. 读取变量

$变量名

变量名也可以使用花括号${}包围。这种写法可以用于变量名与其他字符连用的情况。

$ echo $a_file

$ echo ${a}_file
foo_file

如果变量的值本身也是变量,可以使用${!varname}的语法,读取最终的值。

$ myvar=USER
$ echo ${!myvar}
ruanyf
4. 删除变量
unset NAME

删除一个变量,也可以将这个变量设成空字符串。

$ foo=''
$ foo=
5. 输出变量

为了把变量传递给子 Shell,需要使用export命令, 对于子 Shell 来说就是环境变量。

export NAME=value
6. 特殊变量
  • $? : 上一个命令的退出码,用来判断上一个命令是否执行成功。0 表示成功.
  • $$ : 当前Shell 的进程ID.
  • $_ : 上一个命令的最后一个参数.
  • $! : 最近一个后台执行的异步命令的进程ID.
  • $0 : 当前Shell 名称.
  • $- : 当前Shell 的启动参数.
  • $@ : 脚本参数数量.
  • $# : 脚本参数值.
7. 变量默认值
  • 返回一个默认值
${varname:-word}

如果变量varname存在且不为空,则返回它的值,否则返回word。它的目的是返回一个默认值,比如${count:-0}表示变量count不存在时返回0。

  • 设置变量默认值
${varname:=word}

如果变量varname存在且不为空,则返回它的值,否则将它设为word,并且返回word。它的目的是设置变量的默认值,比如${count:=0}表示变量count不存在时返回0,且将count设为0

  • 检测变量是否存在
${varname:+word}

如果变量名存在且不为空,则返回word,否则返回空值。它的目的是测试变量是否存在,比如${count:+1}表示变量count存在时返回1(表示true),否则返回空值。

  • 变量存在则返回变量值 , 变量不存在 , 返回message
${varname:?message}

如果变量varname存在且不为空,则返回它的值,否则打印出varname: message,并中断脚本的执行。如果省略了message,则输出默认的信息“parameter null or not set.”。它的目的是防止变量未定义,比如${count:?"undefined!"}表示变量count未定义时就中断执行,抛出错误,返回给定的报错信息undefined!

  • 脚本参数

如果用在脚本中,变量名的部分可以用数字19,表示脚本的参数。

filename=${1:?"filename missing."}

上面代码出现在脚本中,1表示脚本的第一个参数。如果该参数不存在,就退出脚本并报错。

8. declare 命令

declare命令用于声明变量及其属性。

declare [options] variable=value

declare命令的主要参数(OPTION)如下。

  • -a:声明数组变量。
  • -f:输出所有函数定义。
  • -F:输出所有函数名。
  • -i:声明整数变量。-i参数声明整数变量以后,可以直接进行数学运算。
  • -l:声明变量为小写字母。可以自动把变量值转成小写字母。
  • -p:查看变量信息。
  • -r:声明只读变量。无法改变变量值,也不能unset变量。
  • -u:声明变量为大写字母。可以自动把变量值转成大写字母。
  • -x:该变量输出为环境变量。
declare -i num=5   # 声明一个整数
declare -a arr=(1 2 3)  # 声明一个数组
declare -r readonly_var="This is read-only"  # 声明一个只读变量
declare -x export_var="Exported variable"  # 将变量标记为环境变量
9. readonly 命令

readonly命令等同于declare -r,用来声明只读变量,不能改变变量值,也不能unset变量。

10. let 命令

let 是一个用于执行算术运算和赋值的命令。它可以用来进行整数运算,而且它是可选的,因为在许多情况下,Bash 变量也可以直接进行算术运算而无需 let

基本语法如下:

let expression

其中,expression 是一个包含算术表达式的字符串。let 将计算该表达式并将结果赋给变量。以下是一些示例:

# 使用 let 进行整数运算
let "sum = 5 + 3"
echo $sum  # 输出: 8

# 变量的自增
let "counter++"
echo $counter  # 输出: 1

# 使用变量进行计算
num1=10
num2=5
let "result = num1 * num2"
echo $result  # 输出: 50

let 也可以用于在条件语句中进行算术比较:

# 使用 let 进行算术比较
num1=10
num2=20
if let "num1 < num2"; then
  echo "num1 is less than num2"
fi

虽然 let 在某些情况下很有用,但在 Bash 中,通常也可以直接使用 $(( ))(( )) 来执行算术运算,而无需使用 let。例如:

# 使用 $(( )) 进行算术运算
result=$((num1 + num2))
echo $result  # 输出: 30

或者

# 使用 (( )) 进行算术运算
((result = num1 * num2))
echo $result  # 输出: 50

这两种方式都是执行算术运算的常见形式,而 let 是其中之一。

6. 字符串操作

1. 获取字符串长度

${#varname}

2. 提取子字符串

${varname:offset:length}
# varname只能是变量
# offset可以为负, 从后往前数,但是还是从前往后截取.
# length可省略, 截取到末尾
foo="This string is long."
echo ${foo: -5:-2}
lon

3. 搜索和替换

1. 字符串头部的模式匹配

如果匹配成功,就删除匹配的部分,返回剩下的部分。原始变量不会发生变化。

# 如果 pattern 匹配变量 variable 的开头,
# 删除最短匹配(非贪婪匹配)的部分,返回剩余部分
${variable#pattern}

# 如果 pattern 匹配变量 variable 的开头,
# 删除最长匹配(贪婪匹配)的部分,返回剩余部分
${variable##pattern}

myPath=/home/cam/book/long.file.name
echo ${myPath#/*/}
cam/book/long.file.name
echo ${myPath##/*/}
long.file.name

替换头部部分:

# 模式必须出现在字符串的开头
${variable/#pattern/string}

# 示例
foo=JPG.JPG
echo ${foo/#JPG/jpg}
jpg.JPG
2. 尾部模式匹配

如果匹配成功,就删除匹配的部分,返回剩下的部分。原始变量不会发生变化。

# 如果 pattern 匹配变量 variable 的结尾,
# 删除最短匹配(非贪婪匹配)的部分,返回剩余部分
${variable%pattern}

# 如果 pattern 匹配变量 variable 的结尾,
# 删除最长匹配(贪婪匹配)的部分,返回剩余部分
${variable%%pattern}

将尾部匹配的部分,替换成其他内容

# 模式必须出现在字符串的结尾
${variable/%pattern/string}
# 示例
$ foo=JPG.JPG
$ echo ${foo/%JPG/jpg}
JPG.jpg
3. 任意位置模式匹配
# 如果 pattern 匹配变量 variable 的一部分,
# 最长匹配(贪婪匹配)的那部分被 string 替换,但仅替换第一个匹配
${variable/pattern/string}

# 如果 pattern 匹配变量 variable 的一部分,
# 最长匹配(贪婪匹配)的那部分被 string 替换,所有匹配都替换
${variable//pattern/string}

4. 大小写

# 转为大写
${varname^^}

# 转为小写
${varname,,}

7. 算术运算

((...))
# 引用 算术运算结果:
$((...))
  • 进制
  • 赋值运算
  • 算数运算
  • 位运算
  • 逻辑运算

8. 操作历史

  1. ctrl + r : 搜索命令历史
  2. !! 上一条指令
# 报错,没有执行权限
$ yum update

$ sudo !!
sudo yum update
  1. !$ 和 !*

!$代表上一个命令的最后一个参数,它的另一种写法是$_

!*代表上一个命令的所有参数,即除了命令以外的所有部分。

9. 行操作

  1. alt + f : 移动到当前单词的词尾.
  2. alt +b : 移动到当前单词的词首.

10. 目录堆栈

  1. **dirs **: 查看目录堆栈
  2. **pushd dirname **: 压入目录栈, 并cd到该目录
  3. popd : 弹出 目录栈, 并cd 到 上一个目录.

11. 脚本入门

1. Shebang 行
#!/bin/bash
2. 执行权限和路径
chmod 755 script.sh
5. 脚本参数
  • $0:脚本文件名,即script.sh
  • $1~$9:对应脚本的第一个参数到第九个参数。
  • $#:参数的总数。
  • $@:全部的参数,参数之间使用空格分隔。
  • $*:全部的参数,参数之间使用变量$IFS值的第一个字符分隔,默认为空格,但是可以自定义。

用户可以输入任意数量的参数,利用for循环,可以读取每一个参数。

#!/bin/bash

for i in "$@"; do
  echo $i
done
6. shift命令

shift 命令移除一个参数, 使得后面的参数向前一位

#!/bin/bash

echo "一共输入了 $# 个参数"

while [ "$1" != "" ]; do
  echo "剩下 $# 个参数"
  echo "参数:$1"
  shift
done
7. getopts

getopts 取出 脚本所有的带有前置连词线(-)的参数。

while getopts 'lha:' OPTION; do # 冒号表示有值参数,可以在中间
  case "$OPTION" in
    l)
      echo "linuxconfig"
      ;;

    h)
      echo "h stands for h"
      ;;

    a)
      avalue="$OPTARG" # 赋值语句没有空格
      echo "The value provided is $OPTARG"
      ;;
    ?)
      echo "script usage: $(basename $0) [-l] [-h] [-a somevalue]" >&2
      exit 1
      ;;
  esac
done
shift "$(($OPTIND - 1))" # 移除带前置线的参数,保证脚本可以处理主参数
8. 配置项参数终止符

-- : 将其后的参数作为实体参数解释, 而不是配置项。

# 情况1: 文件名中是--开头
cat -- --file
# 情况2: myPath中,可能- 或 -- 开头
myPath="-l"
$ ls -- $myPath
ls: 无法访问'-l': 没有那个文件或目录
# 情况3: 要搜索的内容是 - 或 -- 开头
grep -- "--hello" example.txt
9. exit命令

exit 0 : 成功退出.

10. 命令执行结果

$? 是上一条命令的执行结果( 0 表示成功)

11. source 命令

.(省略写法): 在当前bash 执行一个脚本.

12. alias 别名
alias NAME=DEFINITION

12. read 命令

1. read 用法

read 命令 读入数据.

read [-options] [variable...]
# read可以接受用户输入的多个值。
read FN LN
echo "Hi! $LN, $FN !"
# 如果read命令之后没有定义变量名,那么环境变量REPLY会包含所有的输入。
echo -n "Enter one or more values > "
read
echo "REPLY = '$REPLY'"

read 读取 文件内容

#!/bin/bash

filename='/etc/hosts'

while read myline
do
  echo "$myline"
done < $filename
2. 参数
  1. -t 设置超时秒数 read -t 3 response
  2. -p 指定用户输入提示. read -p " please enter xxx >"
  3. -a 将输入赋值给数组. read -a people ,echo ${people[2]}
  4. -n 只读取 n 个 字符. read -n 3 letter
  5. -e 允许用户使用readline 快捷键.

不常用:

  1. -d 定义输入结束符(默认是换行符)
  2. -r raw模式,不把反斜杠转义
  3. -u fd : 使用文件描述符 fd 作为输入.
3. IFS 变量

read命令读取的值,默认是以空格分隔。可以通过自定义环境变量IFS(内部字段分隔符,Internal Field Separator 的缩写),修改分隔标志。

#!/bin/bash
# read-ifs: read fields from a file

FILE=/etc/passwd

read -p "Enter a username > " user_name
file_info="$(grep "^$user_name:" $FILE)"

if [ -n "$file_info" ]; then
  IFS=":" read user pw uid gid name home shell <<< "$file_info"
  echo "User = '$user'"
  echo "UID = '$uid'"
  echo "GID = '$gid'"
  echo "Full Name = '$name'"
  echo "Home Dir. = '$home'"
  echo "Shell = '$shell'"
else
  echo "No such user '$user_name'" >&2
  exit 1
fi

上面例子中,IFS设为冒号,然后用来分解/etc/passwd文件的一行。IFS的赋值命令和read命令写在一行,这样的话,IFS的改变仅对后面的命令生效.

<<<是 Here 字符串,用于将变量值转为标准输入,因为read命令只能解析标准输入。

如果IFS设为空字符串,就等同于将整行读入一个变量。

#!/bin/bash
input="/path/to/txt/file"
while IFS= read -r line
do
  echo "$line"
done < "$input"

13. 条件判断

1. if 结构
if commands; then
  commands
[elif commands; then
  commands...]
[else
  commands]
fi
2. test命令
# 写法一
test expression
# 写法二
[ expression ]
# 写法三
[[ expression ]] # 支持正则判断
3. 判断表达式
3.1 文件判断
#!/bin/bash

FILE=~/.bashrc

if [ -e "$FILE" ]; then
  if [ -f "$FILE" ]; then
    echo "$FILE is a regular file."
  fi
  if [ -d "$FILE" ]; then
    echo "$FILE is a directory."
  fi
  if [ -r "$FILE" ]; then
    echo "$FILE is readable."
  fi
  if [ -w "$FILE" ]; then
    echo "$FILE is writable."
  fi
  if [ -x "$FILE" ]; then
    echo "$FILE is executable/searchable."
  fi
else
  echo "$FILE does not exist"
  exit 1
fi
  • [ -a file ]:如果 file 存在,则为true
  • [ -b file ]:如果 file 存在并且是一个块(设备)文件,则为true
  • [ -c file ]:如果 file 存在并且是一个字符(设备)文件,则为true
  • [ -d file ]:如果 file 存在并且是一个目录,则为true
  • [ -e file ]:如果 file 存在,则为true
  • [ -f file ]:如果 file 存在并且是一个普通文件,则为true
  • [ -g file ]:如果 file 存在并且设置了组 ID,则为true
  • [ -G file ]:如果 file 存在并且属于有效的组 ID,则为true
  • [ -h file ]:如果 file 存在并且是符号链接,则为true
  • [ -k file ]:如果 file 存在并且设置了它的“sticky bit”,则为true
  • [ -L file ]:如果 file 存在并且是一个符号链接,则为true
  • [ -N file ]:如果 file 存在并且自上次读取后已被修改,则为true
  • [ -O file ]:如果 file 存在并且属于有效的用户 ID,则为true
  • [ -p file ]:如果 file 存在并且是一个命名管道,则为true
  • [ -r file ]:如果 file 存在并且可读(当前用户有可读权限),则为true
  • [ -s file ]:如果 file 存在且其长度大于零,则为true
  • [ -S file ]:如果 file 存在且是一个网络 socket,则为true
  • [ -t fd ]:如果 fd 是一个文件描述符,并且重定向到终端,则为true。 这可以用来判断是否重定向了标准输入/输出/错误。
  • [ -u file ]:如果 file 存在并且设置了 setuid 位,则为true
  • [ -w file ]:如果 file 存在并且可写(当前用户拥有可写权限),则为true
  • [ -x file ]:如果 file 存在并且可执行(有效用户有执行/搜索权限),则为true
  • [ FILE1 -nt FILE2 ]:如果 FILE1 比 FILE2 的更新时间更近,或者 FILE1 存在而 FILE2 不存在,则为true
  • [ FILE1 -ot FILE2 ]:如果 FILE1 比 FILE2 的更新时间更旧,或者 FILE2 存在而 FILE1 不存在,则为true
  • [ FILE1 -ef FILE2 ]:如果 FILE1 和 FILE2 引用相同的设备和 inode 编号,则为true
3.2 字符串判断
#!/bin/bash

ANSWER=maybe

if [ -z "$ANSWER" ]; then
  echo "There is no answer." >&2
  exit 1
fi
if [ "$ANSWER" = "yes" ]; then
  echo "The answer is YES."
elif [ "$ANSWER" = "no" ]; then
  echo "The answer is NO."
elif [ "$ANSWER" = "maybe" ]; then
  echo "The answer is MAYBE."
else
  echo "The answer is UNKNOWN."
fi
  • [ string ]:如果string不为空(长度大于0),则判断为真。
  • [ -n string ]:如果字符串string的长度大于零,则判断为真。
  • [ -z string ]:如果字符串string的长度为零,则判断为真。
  • [ string1 = string2 ]:如果string1string2相同,则判断为真。
  • [ string1 == string2 ] 等同于[ string1 = string2 ]
  • [ string1 != string2 ]:如果string1string2不相同,则判断为真。
  • [ string1 '>' string2 ]:如果按照字典顺序string1排列在string2之后,则判断为真。
  • [ string1 '<' string2 ]:如果按照字典顺序string1排列在string2之前,则判断为真。
3.3 整数判断
#!/bin/bash

INT=-5

if [ -z "$INT" ]; then
  echo "INT is empty." >&2
  exit 1
fi
if [ $INT -eq 0 ]; then
  echo "INT is zero."
else
  if [ $INT -lt 0 ]; then
    echo "INT is negative."
  else
    echo "INT is positive."
  fi
  if [ $((INT % 2)) -eq 0 ]; then
    echo "INT is even."
  else
    echo "INT is odd."
  fi
fi
  • [ integer1 -eq integer2 ]:如果integer1等于integer2,则为true
  • [ integer1 -ne integer2 ]:如果integer1不等于integer2,则为true
  • [ integer1 -le integer2 ]:如果integer1小于或等于integer2,则为true
  • [ integer1 -lt integer2 ]:如果integer1小于integer2,则为true
  • [ integer1 -ge integer2 ]:如果integer1大于或等于integer2,则为true
  • [ integer1 -gt integer2 ]:如果integer1大于integer2,则为true
3.4 正则判断
[[ string1 =~ regex ]]

regex是一个正则表示式,=~是正则比较运算符。

#!/bin/bash

INT=-5

if [[ "$INT" =~ ^-?[0-9]+$ ]]; then
  echo "INT is an integer."
  exit 0
else
  echo "INT is not an integer." >&2
  exit 1
fi
3.5 test的逻辑运算
  • AND运算:符号&&,也可使用参数-a
  • OR运算:符号||,也可使用参数-o
  • NOT运算:符号!
if !([ $INT -ge $MIN_VAL ] && [ $INT -le $MAX_VAL ]); then
  echo "$INT is outside $MIN_VAL to $MAX_VAL."
else
  echo "$INT is in range."
fi

圆括号 ( ) 在这里可以用来创建一个子shell,从而限定否定的范围。

3.6 算术运算

((...))进行算术运算的判断。只要是算术表达式,都能用于((...))语法.

if (( ((INT % 2)) == 0)); then
      echo "INT is even."
    else
      echo "INT is odd."
    fi

如果算术计算的结果是非零值,则表示判断成立。这一点跟命令的返回值正好相反,需要小心。

4. 普通命令的逻辑运算
# 对于&&操作符,先执行command1,执行成功后,才会执行command2。
$ command1 && command2
# 对于||操作符,先执行command1,执行失败后,才会执行command2。
$ command1 || command2

示例:

mkdir temp && cd temp
[ -d temp ] || mkdir temp

if grep $word1 $filename && grep $word2 $filename
then
  echo "$word1 and $word2 are both in $filename."
fi

[[ -d "$dir_name" ]] && cd "$dir_name" && rm *
5. case 结构
case expression in
  pattern ) # pattern 可以使用 模式扩展
    commands ;;
  pattern )
    commands ;;
  ...
esac

示例:

#!/bin/bash

OS=$(uname -s)

case "$OS" in
  FreeBSD) echo "This is FreeBSD" ;;
  Darwin) echo "This is Mac OSX" ;;
  AIX) echo "This is AIX" ;;
  Minix) echo "This is Minix" ;;
  Linux) echo "This is Linux" ;;
  *) echo "Failed to identify this OS" ;;
esac

Bash 4.0之前,case结构只能匹配一个条件,然后就会退出case结构。Bash 4.0之后,允许匹配多个条件,这时可以用;;&终止每个条件块。

read -n 1 -p "Type a character > "
echo
case $REPLY in
  [[:upper:]])    echo "'$REPLY' is upper case." ;;&
  [[:lower:]])    echo "'$REPLY' is lower case." ;;&
  [[:alpha:]])    echo "'$REPLY' is alphabetic." ;;&
  [[:digit:]])    echo "'$REPLY' is a digit." ;;&
  [[:graph:]])    echo "'$REPLY' is a visible character." ;;&
  [[:punct:]])    echo "'$REPLY' is a punctuation symbol." ;;&
  [[:space:]])    echo "'$REPLY' is a whitespace character." ;;&
  [[:xdigit:]])   echo "'$REPLY' is a hexadecimal digit." ;;&
esac

14. 循环

1. while 循环
while condition; do
  commands
done

示例:

#!/bin/bash

number=0
while [ "$number" -lt 10 ]; do
  echo "Number = $number"
  number=$((number + 1))
done
2. for…in 循环

for...in循环用于遍历列表的每一项。

for variable in list; do
  commands
done
# in list 省略时,默认是脚本所有参数$@.

示例:

#!/bin/bash

count=0
for i in $(cat ~/.bash_profile); do
  count=$((count + 1))
  echo "Word $count ($i) contains $(echo -n $i | wc -c) characters"
done

上面例子中,cat ~/.bash_profile命令会输出~/.bash_profile文件的内容,然后通过遍历每一个词,计算该文件一共包含多少个词,以及每个词有多少个字符。

3. for 循环
for (( expression1; expression2; expression3 )); do
  commands
done
4. break, continue
5. select 结构
select name [in list]
do
  commands
done

Bash 会对select依次进行下面的处理。

  1. select生成一个菜单,内容是列表list的每一项,并且每一项前面还有一个数字编号。
  2. Bash 提示用户选择一项,输入它的编号。
  3. 用户输入以后,Bash 会将该项的内容存在变量name,该项的编号存入环境变量REPLY。如果用户没有输入,就按回车键,Bash 会重新输出菜单,让用户选择。
  4. 执行命令体commands
  5. 执行结束后,回到第一步,重复这个过程。

示例;

#!/bin/bash

echo "Which Operating System do you like?"

select os in Ubuntu LinuxMint Windows8 Windows10 WindowsXP
do
  case $os in
    "Ubuntu"|"LinuxMint")
      echo "I also use $os."
    ;;
    "Windows8" | "Windows10" | "WindowsXP")
      echo "Why don't you try Linux?"
    ;;
    *)
      echo "Invalid entry."
      break
    ;;
  esac
done

15. 函数

1. 函数定义

函数与脚本的区别是; 1. 函数在当前Shell执行,2. 脚本 在 子Bash 执行.

[function] fn() { # fn 是函数名
  # codes
}

函数调用方法和脚本一致.


2. 参数变量

函数参数 和 脚本参数 一致.

3. return 命令

返回值必须是数字.通过$? 拿出.

4. 全局变量和局部变量

bash内, 不论是否在函数内声明的变量, 默认是全局变量.

# 声明局部变量需要使用 local 关键字
fn () {
local var_name
}

16. 数组

1. 创建数组
ARRAY[INDEX]=value

ARRAY=(
  value1
  value2
  value3
)

array=([2]=c [0]=a [1]=b)

mp3s=( *.mp3 )

declare -a ARRAYNAME
# read -a命令则是将用户的命令行输入,存入一个数组。
read -a dice
2. 读取数组
echo ${array[i]}
# 读取所有成员
echo ${foo[@]}
# 不加双引号, @ 和 * 一样, 加双引号, @ 分开处理每个成员, * 将所有成员当成一个字符串.
for i in "${names[@]}"; do # 双引号保护字符串、变量和命令, 
  echo $i
done
# 拷贝一个数组的最好方法: 
hobbies=( "${activities[@]}" )

# 赋值,引用时: 默认位置是array[0]
declare -a foo
foo=A
echo ${foo[0]}
A
3. 数组长度
${#array[*]}
${#array[@]}
# 返回字符串长度:
echo ${#a[100]}
4. 返回数组有成员的序号
${!array[@]}
${!array[*]}
5. 提取数组成员
${array[@]:position:length}
6. 追加数组成员
foo+=(d e f)
7. 删除数组
unset foo[2]
# 数组成员变成空值, 但是成员依然存在
foo[1]=''
# 清空数组
unset ArrayName

8. 关联数组(键值)
declare -A colors
colors["red"]="#ff0000"
colors["green"]="#00ff00"
colors["blue"]="#0000ff"

echo ${colors["blue"]}

17. set命令, shopt命令

1. set

1.1 常用命令

修改子Shell的运行参数

# 如果遇到不存在的变量,则报错(默认是忽略)。
set -u
# 执行命令前,先输出执行的哪条命令
set -x
# 关闭命令输出:
set +x

# 发生错误,则终止执行
set -e
# 发生错误继续执行
set +e
# 推荐写法
command || true
command1 && command2

# 当使用 | 管道 组合命令时, 只要最后一个命令成功,则认为该命令成功. set -e 失效.
# 当前管道命令(视为一个命令)还会执行完, 但是后续命令不执行.
set -o pipefail

# 纠正错误不会被trap捕获
set -E

示例:

#!/bin/bash
set -Eeuo pipefail

trap "echo ERR trap fired!" ERR

myfunc()
{
  # 'foo' 是一个不存在的命令
  foo
}
myfunc

$ bash test.sh
test.sh:行9: foo:未找到命令
ERR trap fired!
1.2 其他命令

set命令还有一些其他参数。

  • set -n:等同于set -o noexec,不运行命令,只检查语法是否正确。
  • set -f:等同于set -o noglob,表示不对通配符进行文件名扩展。
  • set -v:等同于set -o verbose,表示打印 Shell 接收到的每一行输入。
  • set -o noclobber:防止使用重定向运算符>覆盖已经存在的文件。

上面的-f-v参数,可以分别使用set +fset +v关闭。

1.3 set 命令总结
# 写法一
set -Eeuxo pipefail
# 写法二
set -Eeux
set -o pipefail

2. 错误处理

如果遇到运行失败的命令(返回值非0),Bash 默认会继续执行后面的命令。

# 建议写法:
command || exit 1
command1 && command2

3. shopt 命令

shopt命令用来调整 Shell 的参数,跟set命令的作用很类似。set是从 Ksh 继承的,属于 POSIX 规范的一部分,而shopt是 Bash 特有的。

# 查看参数状态
shopt
shopt globstar
# 打开某个参数
shopt -s optionNameHere
# 关闭某个参数
shopt -u optionNameHere
# 查询某个参数是否打开
shopt -q globstar
# 通过命令的执行状态($?)表示查询结果。如果状态为0,表示该参数打开;如果为1,表示该参数关闭。

示例:

if (shopt -q globstar); then
  ...
fi

18. 脚本除错

1. 常见错误

建议写法:

# 只打印要删除的内容,不删除
[[ -d $dir_name ]] && cd $dir_name && echo rm *

2. bash -x 参数

输出的命令之前的+号,是由系统变量PS4决定,可以修改这个变量。

$ export PS4='$LINENO + '
$ trouble
5 + number=1
7 + '[' 1 = 1 ']'
8 + echo 'Number is equal to 1.'
Number is equal to 1.

3. 环境变量

3.1 LINENO

变量LINENO返回它在脚本里面的行号。

3.2 FUNCNAME

变量FUNCNAME返回一个数组,内容是当前的函数调用堆栈。

3.3 BASH_SOURCE

变量BASH_SOURCE返回一个数组,内容是当前的脚本调用堆栈。

3.4 BASH_LINENO

变量BASH_LINENO返回一个数组,内容是每一轮调用对应的行号。

19. mktemp命令, trap命令

1. 临时文件的安全问题

对于临时文件,使用mktemp命令是最安全的做法。

生成临时文件应该遵循下面的规则。

  • 创建前检查文件是否已经存在。
  • 确保临时文件已成功创建。
  • 临时文件必须有权限的限制。
  • 临时文件要使用不可预测的文件名。
  • 脚本退出时,要删除临时文件(使用trap命令)。

2. mktemp 命令

2.1 mktemp命令用法

mktemp命令就是为安全创建临时文件而设计的。

#!/bin/bash

trap 'rm -f "$TMPFILE"' EXIT

TMPFILE=$(mktemp) || exit 1
echo "Our temp file is $TMPFILE"
2.2 命令参数
# -d 参数可以创建一个临时目录。
$ mktemp -d
/tmp/tmp.Wcau5UjmN6
# -p 参数可以指定临时文件所在的目录。默认是使用$TMPDIR环境变量指定的目录(/tmp).
$ mktemp -p /home/ruanyf/
/home/ruanyf/tmp.FOKEtvs2H3
# -t参数可以指定临时文件的文件名模板,模板的末尾必须至少包含三个连续的X字符.
$ mktemp -t mytemp.XXXXXXX
/tmp/mytemp.yZ1HgZV

2. trap命令

trap命令用来在 Bash 脚本中响应系统信号。

trap [动作] [信号1] [信号2] ...

#!/bin/bash

trap 'rm -f "$TMPFILE"' EXIT

TMPFILE=$(mktemp) || exit 1
ls /etc > $TMPFILE
if grep -qi "kernel" $TMPFILE; then
  echo 'find'
fi

如果trap需要触发多条命令,可以封装一个 Bash 函数。

function egress {
  command1
  command2
  command3
}

trap egress EXIT

20. 启动环境

21. 命令提示符

  • 30
    点赞
  • 27
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值