解释器

#! 告诉系统其后路径所指定的程序即是解释此脚本文件的 Shell 解释器

  1. # 以下两种方式都可以指定 shell 解释器为 bash,
  2. # 第二种方式更好,系统会自动在 PATH 环境变量中查找你指定的程序
  3. #!/bin/bash
  4. #!/usr/bin/env bash

注释

  • 单行注释 - 以 # 开头,到行尾结束。
  • 多行注释 - 以 :<<EOF 开头,到 EOF 结束。

基本语法

echo

输出普通字符串:

  1. echo "hello, world"
  2. # Output: hello, world

输出含特殊字符的字符串:

  1. echo "hello, \"zp\""
  2. # Output: hello, "zp"

输出含变量的字符串:

  1. name=zp
  2. echo "hello, \"${name}\""
  3. # Output: hello, "zp"

输出含换行符的字符串:

  1. # 输出含换行符的字符串
  2. echo "YES\nNO"
  3. # Output: YES\nNO
  4. echo -e "YES\nNO" # -e 开启转义
  5. # Output:
  6. # YES
  7. # NO

输出含不换行符的字符串:

  1. echo "YES"
  2. echo "NO"
  3. # Output:
  4. # YES
  5. # NO
  6. echo -e "YES\c" # -e 开启转义 \c 不换行
  7. echo "NO"
  8. # Output:
  9. # YESNO

输出重定向至文件

  1. echo "test" > test.txt

输出执行结果

  1. echo `pwd`
  2. # Output:(当前目录路径)

变量

变量类型

  • 局部变量

仅在某个脚本内部有效的变量

  • 环境变量

可以使用export创建环境变量
系统自带环境变量

$HOME 当前用户的用户目录
$PATH 用分号分隔的目录列表,shell 会到这些目录中查找命令
$PWD 当前工作目录
$RANDOM 0 到 32767 之间的整数
$UID 数值类型,当前用户的用户 ID

声明和访问变量

访问变量的语法形式为:${var}$var
变量名外面的花括号是可选的,加不加都行,加花括号是为了帮助解释器识别变量的边界,所以推荐加花括号。

  1. word="hello"
  2. echo ${word}

只读变量

  1. rword="hello"
  2. echo ${rword}
  3. readonly rword
  4. # rword="bye" # 如果放开注释,执行时会报错

删除变量

  1. unset dword # 删除变量
  2. echo ${dword}
  3. # Output: (空)

字符串

shell 字符串可以用单引号 '',也可以用双引号 “”,也可以不用引号。

  • 单引号的特点
    • 单引号里不识别变量
    • 单引号里不能出现单独的单引号(使用转义符也不行),但可成对出现,作为字符串拼接使用。
  • 双引号的特点
    • 双引号里识别变量
    • 双引号里可以出现转义字符

综上,推荐使用双引号。
image.png

获取字符串长度

  1. text="12345"
  2. echo ${#text}
  3. # Output:
  4. # 5

截取字符串长度

  1. text="12345"
  2. echo ${text:2:2}
  3. # Output:
  4. # 34

查找子字符串

  1. #!/usr/bin/env bash
  2. text="hello"
  3. echo `expr index "${text}" ll`
  4. # Execute: ./str-demo5.sh
  5. # Output:
  6. # 3

查找 ll 子字符在 hello 字符串中的起始位置。

数组

创建数组

  1. # 两种方式
  2. nums=([2]=2 [0]=0 [1]=1)
  3. colors=(red yellow "dark blue")

访问数组

最好使用{}

  1. echo ${nums[1]}
  2. # Output: 1
  3. # 访问所有数据
  4. echo ${colors[*]}
  5. # Output: red yellow dark blue
  6. echo ${colors[@]}
  7. # Output: red yellow dark blue

访问数组长度

  1. echo ${#nums[*]}
  2. # Output:
  3. # 3

增加元素

  1. colors=(white "${colors[@]}" green black)

删除元素

  1. unset nums[0]

遍历数组

  1. for i in ${my_arry[@]};
  2. do
  3. echo $i
  4. done

运算符

算数运算符

执行方式: val=expr ${x} + ${y} (这种方式需要转义)
运算符: + - * / %
或者
在算数表达式中,使用变量无需带上$前缀:

  1. x=4
  2. y=7
  3. echo $(( x + y )) ### 11
  4. echo $(( ++x + y++ )) ### 12
  5. echo $(( x + y )) ### 13
  6. #另一种方式
  7. echo $[ $x + $y ]
  8. 不能使用 [[ $x + $y ]]

条件运算符

字符比较

== 相等。用于比较两个数字,相同则返回 true。 [ $x == $y ]
返回 false。
!= 不相等。用于比较两个数字,不相同则返回 true。 [ $x != $y ]
返回 true。

数值比较

-eq 检测两个数是否相等,相等返回 true。 [ $a -eq $b ]
返回 false。
-ne 检测两个数是否相等,不相等返回 true。 [ $a -ne $b ]
返回 true。
-gt 检测左边的数是否大于右边的,如果是,则返回 true。 [ $a -gt $b ]
返回 false。
-lt 检测左边的数是否小于右边的,如果是,则返回 true。 [ $a -lt $b ]
返回 true。
-ge 检测左边的数是否大于等于右边的,如果是,则返回 true。 [ $a -ge $b ]
返回 false。
-le 检测左边的数是否小于等于右边的,如果是,则返回 true。 [ $a -le $b ]返回 true。

:::info 注意:只支持整数,不支持浮点数,不支持字符比较
条件表达式要放在方括号之间,并且要有空格
例如: [ $x == $y ]是错误的,必须写成 [ $x == $y ]。 :::

  1. if [[ ${x} == ${y} ]]
  2. then
  3. echo "${x} = ${y}"
  4. fi
  5. if [[ ${x} != ${y} ]]
  6. then
  7. echo "${x} != ${y}"
  8. fi
  9. if [[ ${x} -ge ${y} ]]; then
  10. echo "${x} -ge ${y}: x 大于或等于 y"
  11. else
  12. echo "${x} -ge ${y}: x 小于 y"
  13. fi
  14. if [[ ${x} -le ${y} ]]; then
  15. echo "${x} -le ${y}: x 小于或等于 y"
  16. else
  17. echo "${x} -le ${y}: x 大于 y"
  18. fi

逻辑运算符

! 非运算,表达式为 true 则返回 false,否则返回 true。 [ ! false ]
返回 true。
-o 或运算,有一个表达式为 true 则返回 true。 [ $a -lt 20 -o $b -gt 100 ]
返回 true。
-a 与运算,两个表达式都为 true 才返回 true。 [ $a -lt 20 -a $b -gt 100 ]
返回 false。
&& 逻辑的 AND [[ ${x} -lt 100 && ${y} -gt 100 ]]
返回 false
&#124;&#124; 逻辑的 OR [[ ${x} -lt 100 &#124;&#124; ${y} -gt 100 ]]
返回 true
  1. if [[ ${x} -lt 100 && ${y} -gt 100 ]]
  2. then
  3. echo "${x} -lt 100 && ${y} -gt 100 返回 true"
  4. else
  5. echo "${x} -lt 100 && ${y} -gt 100 返回 false"
  6. fi
  7. if [[ ${x} -lt 100 || ${y} -gt 100 ]]
  8. then
  9. echo "${x} -lt 100 || ${y} -gt 100 返回 true"
  10. else
  11. echo "${x} -lt 100 || ${y} -gt 100 返回 false"
  12. fi

字符串运算符

-z 检测字符串长度是否为 0,为 0 返回 true。 [ -z $a ]
返回 false。
-n 检测字符串长度是否为 0,不为 0 返回 true。 [ -n $a ]
返回 true。
str 检测字符串是否为空,不为空返回 true。 [ $a ]
返回 true。

文件测试运算符

操作符 说明 举例
-b file 检测文件是否是块设备文件,如果是,则返回 true。 [ -b $file ]
返回 false。
-c file 检测文件是否是字符设备文件,如果是,则返回 true。 [ -c $file ]
返回 false。
-d file 检测文件是否是目录,如果是,则返回 true。 [ -d $file ]
返回 false。
-f file 检测文件是否是普通文件(既不是目录,也不是设备文件),如果是,则返回 true。 [ -f $file ]
返回 true。
-g file 检测文件是否设置了 SGID 位,如果是,则返回 true。 [ -g $file ]
返回 false。
-k file 检测文件是否设置了粘着位(Sticky Bit),如果是,则返回 true。 [ -k $file ]
返回 false。
-p file 检测文件是否是有名管道,如果是,则返回 true。 [ -p $file ]
返回 false。
-u file 检测文件是否设置了 SUID 位,如果是,则返回 true。 [ -u $file ]
返回 false。
-r file 检测文件是否可读,如果是,则返回 true。 [ -r $file ]
返回 true。
-w file 检测文件是否可写,如果是,则返回 true。 [ -w $file ]
返回 true。
-x file 检测文件是否可执行,如果是,则返回 true。 [ -x $file ]
返回 true。
-s file 检测文件是否为空(文件大小是否大于 0),不为空返回 true。 [ -s $file ]
返回 true。
-e file 检测文件(包括目录)是否存在,如果是,则返回 true。 [ -e $file ]
返回 true。

控制语句

[[ ]]sh中是[ ])包起来的表达式被称作 检测命令基元。

if

形式:
if command
then
commands
fi
if 语句会运行if后面的command,如果该command退出码是0(成功),then部分就会被执行
例如:

  1. if pwd; then
  2. echo some
  3. fi
  4. 输出:
  5. /home/delta/edgex-mongo/script
  6. some

如需进行条件判断,需要借助test命令或者[ ]

单行:

if [[ 表达式 ]]; then … ; fi

多行:

if [[表达式]]; then … elif [[表达式]]; then … else … fi

[ condition ]、[[ condition ]]、(( condition )) 区别
[ condition ] : 可以进行数值、字符、文件比较判断,但对于 “>” 、”<”符号需要转义
[[ condition ]] : 可以进行数值、字符、文件比较判断,同时 “>” 、”<”符号不需要转义,但只能不支持部分数值运算符号(如:val++)
(( condition )):可以进行数值、字符、文件比较判断,支持高级数值运算表达式,支持==不支持-eq等符号
总结:推荐使用 [[ condition ]],对于需要使用高级运算符号的情景,再使用(( condition ))

例:

  1. if [[ 1 -eq 1 ]]; then echo "1 -eq 1 result is: true"; fi
  2. # 写成多行
  3. x=10
  4. y=20
  5. if [[ ${x} > ${y} ]]; then
  6. echo "${x} > ${y}"
  7. elif [[ ${x} < ${y} ]]; then
  8. echo "${x} < ${y}"
  9. else
  10. echo "${x} = ${y}"
  11. fi
  12. # 比较字符串
  13. #如果$A等于a*(字符匹配),那么结果为true
  14. if [ "$A" == "a*" ];then
  15. echo "[ == ]"
  16. fi

case

  1. case in
  2. 模式1)
  3. command1
  4. command2
  5. command3
  6. ;;
  7. 模式2
  8. command1
  9. command2
  10. command3
  11. ;;
  12. *)
  13. command1
  14. command2
  15. command3
  16. ;;
  17. esac

  1. case $x in
  2. 1)
  3. echo value is 1
  4. ;;
  5. 2)
  6. echo value is 2
  7. ;;
  8. *)
  9. echo other value
  10. ;;
  11. esac

for

  1. for arg in elem1 elem2 ... elemN
  2. do
  3. ### 语句
  4. done

elem之间使用空格分割

遍历数字

for i in {1..5}; do echo $i; done

{} 之间使用 ..

遍历数组

arr=( a b c d ) for s in ${arr[*]}; do echo $s; done

遍历文件夹
  1. DIR=/home/zp
  2. for FILE in ${DIR}/*.sh; do
  3. mv "$FILE" "${DIR}/scripts"
  4. done

类C用法
  1. for (( i = 0; i < 10; i++ )); do
  2. echo $i
  3. done

while

  1. while [[ condition ]]
  2. do
  3. ### 语句
  4. done

支持 break continue

until

until循环跟while循环正好相反。它跟while一样也需要检测一个测试条件,但不同的是,只要该条件为 就一直执行循环:

  1. until [[ ${x} -ge 5 ]]; do
  2. echo ${x}
  3. x=`expr ${x} + 1`
  4. done

函数

  1. [ function ] funname [()] {
  2. action;
  3. [return int;]
  4. }
  • function关键字可省略
  • 函数返回值 - return 返回函数返回值,返回值类型只能为整数(0-255)。如果不加 return 语句,shell 默认将以最后一条命令的运行结果,作为函数返回值。
  • 函数返回值在调用该函数后通过 $? 来获得。

简写

funname(){
    ...
}

位置参数

位置参数是在调用一个函数并传给它参数时创建的变量。

变量 描述
$0 脚本名称
$1 … $9 第 1 个到第 9 个参数列表
${10} … ${N} 第 10 个到 N 个参数列表
$*
or
$@
除了
$0
外的所有位置参数
$# 不包括
$0
在内的位置参数的个数
$FUNCNAME 函数名称(仅在函数内部有值)
#!/usr/bin/env bash

x=0
if [[ -n $1 ]]; then
  echo "第一个参数为:$1"
  x=$1
else
  echo "第一个参数为空"
fi

y=0
if [[ -n $2 ]]; then
  echo "第二个参数为:$2"
  y=$2
else
  echo "第二个参数为空"
fi

paramsFunction(){
  echo "函数第一个入参:$1"
  echo "函数第二个入参:$2"
}
paramsFunction ${x} ${y}

===================================
$ ./function-demo2.sh 10 20
第一个参数为:10
第二个参数为:20
函数第一个入参:10
函数第二个入参:20

:::info $ 既可以获取传入脚本的参数,也可以获取传入函数的参数 :::

处理参数

$$ 脚本运行的当前进程 ID 号
$! 后台运行的最后一个进程的 ID 号
$- 返回 Shell 使用的当前选项,与 set 命令功能相同。
$? 函数返回值

shell拓展

大括号

echo beg{i,a,u}n ### begin began begun echo {0..5} ### 0 1 2 3 4 5 echo {00..8..2} ### 00 02 04 06 08

命令置换

echo docker date echo $(date)

单引号和双引号

单引号和双引号之间有很重要的区别。在双引号中,变量引用或者命令置换是会被展开的。在单引号中是不会的。举个例子:

echo "Your home: $HOME" ### Your home: /Users/<username>
echo 'Your home: $HOME' ### Your home: $HOME

当局部变量和环境变量包含空格时,它们在引号中的扩展要格外注意。随便举个例子,假如我们用echo来输出用户的输入:

INPUT="A string  with   strange    whitespace."
echo $INPUT   ### A string with strange whitespace.
echo "$INPUT" ### A string  with   strange    whitespace.

调用第一个echo时给了它 5 个单独的参数 —— $INPUT 被分成了单独的词,echo在每个词之间打印了一个空格。第二种情况,调用echo时只给了它一个参数(整个$INPUT 的值,包括其中的空格)。
来看一个更严肃的例子:

FILE="Favorite Things.txt"
cat $FILE   ### 尝试输出两个文件: `Favorite` 和 `Things.txt`
cat "$FILE" ### 输出一个文件: `Favorite Things.txt`

尽管这个问题可以通过把 FILE 重命名成Favorite-Things.txt来解决,但是,假如这个值来自某个环境变量,来自一个位置参数,或者来自其它命令(find, cat, 等等)呢。因此,如果输入 可能 包含空格,务必要用引号把表达式包起来。

流与重定向

代码 描述符 描述
0 stdin 标准输入
1 stdout 标准输出
2 stderr 标准错误输出
Operator Description
> 重定向输出
&> 重定向输出和错误输出
&>> 以附加的形式重定向输出和错误输出
< 重定向输入,输出到终端
<< Here 文档
语法
<<< Here 字符串
### ls的结果将会被写到list.txt中
ls -l > list.txt

### 将输出附加到list.txt中
ls -a >> list.txt

### 所有的错误信息会被写到errors.txt中
grep da * 2> errors.txt

### 从errors.txt中读取输入
ls -l < errors.txt

command > /dev/null

/dev/null 是一个特殊的文件,写入到它的内容都会被丢弃