Shell 教程
Search…
Shell 教程

博客作者:联系请点击,搬运不易,希望请作者喝咖啡,可以点击联系博客作者

目录

脚本运行

建立一个文本文件demo1,以.sh结尾的或者不需要后缀都可以,下面是demo1的内容:
1
#!/bin/bash
2
NUM=10
3
printf "输出数字$NUM\n"
4
echo $NUM
Copied!
直接运行是会报错误的,因为没有权限执行(Permission denied),需要给权限。下面方式
1
chmod +x ./demo1 # 使每个人都有执行的权限
2
chmod +rx ./demo1 # 使每个人都有读和执行的权限
3
chmod u+rx ./demo1 # 仅仅使脚本文件拥有者有读和执行的权限
4
chmod u+x ./demo1 # 只有自己可以执行,其它人不能执行
5
chmod ug+x ./demo1 # 只有自己以及同一群可以执行,其它人不能执行
6
chmod 555 ./demo1 # 使每个人都有读和执行的权限
7
chmod 777 ./demo1
Copied!
当前目录下运行 demo1 脚本
1
./demo1
2
# 输出结果===>
3
输出数字10
4
10
Copied!
直接运行demo1脚本,这个方法是把脚本给系统中所有其他的用户使用,这个时候需要你将demo1脚本移动到目录/usr/local/bin 中(必须要有root的权限),移动到 bin 目录之后,你只需使用 demo1 加回车就能执行脚本了。
1
demo1
2
3
# 输出结果===>
4
输出数字10
5
10
Copied!
通过sh或者bash命令运行脚本,sh scriptname 运行一个Bash脚本将会禁止所有Bash的扩展特性。
1
# 你能够运行它用命令
2
sh demo1
3
4
# 另外也也可以用bash来执行
5
bash demo1
Copied!
脚本以"#!"行开头,行将会命令解释器(sh或是bash)。#!/bin/rm 当你运行这个脚本时,除了这个脚本消失了之外,你不会发现更多其他的东西。

特殊字符

注释

#开头的行就是注释,会被解释器忽略。注释行前面也可以有空白字符。
1
#--------------------------------------------
2
# 这是一个注释
3
# author:作者
4
# site:https://github.com/jaywcjlove
5
#--------------------------------------------
6
7
echo "A comment will follow." # 这里可以添加注释.
8
# ^ 注意在#前面可以有空白符
Copied!
echo命令给出的一个转义的#字符并不会开始一个注释。同样地,出现在一些参数代换结构和在数值常量表达式中的#字符也同样不会开始一个注释。
1
echo "这里的 # 不会被注释"
2
echo '这里的 # 不会被注释'
3
echo 这里的 \# 不会被注释
4
echo 这里的 # 会被注释
5
6
echo ${PATH#*:} # 前面的#是参数代换,不是注释.
7
echo $(( 2#101011 )) # 基本转换,不是注释.
Copied!

命令分割符

分号;命令分割符,分割符允许在同一行里有两个或更多的命令。执行demo2,会将 demo2 拷贝输出 demo2.bak 。
1
echo hello; echo there # 输出 hello 和 there
2
filename='demo2' # 变量
3
if [ -x "$filename" ]; then # 注意:"if" and "then"需要分隔符
4
# 思考一下这是为什么?
5
echo "File $filename exists."; cp $filename $filename.bak
6
else
7
echo "File $filename not found."; touch $filename
8
fi; echo "File test complete."
Copied!

结束符

双分号;;,case语句分支的结束符。demo3
1
read Keypress
2
case "$Keypress" in
3
[[:lower:]] ) echo "Lowercase letter";;
4
[[:upper:]] ) echo "Uppercase letter";;
5
[0-9] ) echo "Digit";;
6
* ) echo "Punctuation, whitespace, or other";;
7
esac # 允许字符串的范围出现在[]中,
8
#+ 或者POSIX风格的[[中.
9
exit 0
Copied!

句号/圆点

作为一个文件名的组成部分.,当点.以一个文件名为前缀时,起作用使该文件变成了隐藏文件。这种隐藏文件ls一般是不会显示出来的。
作为目录名时,单个点(.)表示当前目录,两个点(..)表示上一级目录(或称为父目录)。
点(.)字符匹配。作为正则表达式的一部分,匹配字符时,单点(.)表示匹配任意一个字符。

引号

引号一个很重要的作用是保护命令行上的一个参数不被shell解释,而把此参数传递给要执行的程序来处理它。
1
bash$ grep '[Ff]ile' demo*
2
demo2:filename='demo2'
3
demo2:if [ -x "$filename" ]; then # 注意:"if" and "then"需要分隔符
4
demo2: echo "File $filename exists."; cp $filename $filename.bak
Copied!
引号能改掉echo's不换行的“习惯”。
1
bash$ echo $(ls -al)
2
total 24 drwxr-xr-x 5 kacperwang staff 170 1 22 16:47 . drwxr-xr-x 5 kacperwang staff 170 1 22 13:29 .. -rwxr-xr-x 1 kacperwang staff 58 1 22 16:20 demo1 -rwxr-xr-x 1 kacperwang staff 325 1 22 16:39 demo2 -rwxr-xr-x 1 kacperwang staff 899 1 22 17:16 demo3
3
4
bash$ echo "$(ls -al)"
5
total 24
6
drwxr-xr-x 5 kacperwang staff 170 1 22 16:47 .
7
drwxr-xr-x 5 kacperwang staff 170 1 22 13:29 ..
8
-rwxr-xr-x 1 kacperwang staff 58 1 22 16:20 demo1
9
-rwxr-xr-x 1 kacperwang staff 325 1 22 16:39 demo2
10
-rwxr-xr-x 1 kacperwang staff 899 1 22 17:16 demo3
Copied!
单引号
1
str='this is a string'
Copied!
单引号里的任何字符都会原样输出,单引号字符串中的变量是无效的 单引号字串中不能出现单引号(对单引号使用转义符后也不行)
双引号
1
your_name='qinjx'
2
str="Hello, I know your are \"$your_name\"! \n"
Copied!
双引号里可以有变量 双引号里可以出现转义字符

命令替换

命令替换"`",将会重新分配一个命令甚至是多个命令的输出;它会将命令的输出如实地添加到另一个上下文中。demo4
1
script_name=`basename $0`
2
echo "The name of this script is $script_name."
3
4
textfile_listing=`ls *`
5
# 变量中包含了当前工作目录下所有的*文件
6
echo $textfile_listing
Copied!
通过这个符号,批量删除文件
1
rm `cat filename` # "filename" 包含了需要被删除的文件列表
2
# 可能会产生"参数列表太长"的错误
3
# 更好的方法是 xargs rm -- < filename
4
# ( -- 同时覆盖了那些以"-"开头的文件所产生的特殊情况 )
Copied!

操作符

赋值

变量赋值,初始化或改变一个变量的值,通用的变量赋值操作符,可以用于数值和字符串的赋值
1
var=27
2
category=minerals # "="字符后面不能加空白字符.
Copied!
不要把"="赋值操作符和=测试操作符搞混了。
1
# = 用于测试操作符
2
if [ "$string1" = "$string2" ]
3
# if [ "X$string1" = "X$string2" ] 会更安全,
4
# 它为了防止其中有一个字符串为空时产生错误信息.
5
# (增加的"X"字符可以互相抵消.)
6
then
7
command
8
fi
Copied!

计算操作符

操作符
描述
操作符
描述
操作符
描述
+
/
**
求幂
-
*
%
求模demo6
1
# Bash在版本2.02引入了"**"求幂操作符.
2
let "z=5**3"
3
echo "z = $z" # z = 125
4
5
# 求模(它返回整数整除一个数后的余数)
6
let "y=5 % 3"
7
echo "y = $y" # y = 2
Copied!
操作符
描述
+=
加等(plus-equal) 把原变量值增加一个常量并重新赋值给变量
-=
减等(minus-equal) 把原变量值减少一个常量并重新赋值给变量
*=
乘等(times-equal) 把原变量值乘上一个常量并重新赋值给变量
/=
除等(slash-equal) 把原变量值除以一个常量并重新赋值给变量
%=
模等(mod-equal) 把原变量值除以一个常量整除(即取模)并重新赋余数的值给变量
1
let "var += 5" # 会使变量var值加了5并把值赋给var.
2
let "var *= 4" # 使变量var的值乘上4并把值赋给var.
Copied!
例子:

位操作符

位操作符很少在脚本中使用。他们主要用于操作和测试从端口或sockets中读到的数据。“位运算”更多地用于编译型的语言,比如说C和C++,它们运行起来快地像飞。
操作符
描述
操作符
描述
<<
位左移(每移一位相当乘以2)
`
`
位或
<<=
位左移赋值
`
=`
位或赋值
>>
位右移(每移一位相当除以2)
~
位反
>>=
"位右移赋值"(和<<=相反)
!
位非
&
位与
^
位或
&=
位于赋值
^=
位或赋值
1
# <<=
2
# "位左移赋值"
3
let "var <<= 2" 结果使var的二进制值左移了二位(相当于乘以4)
Copied!

逻辑操作符

逻辑与&&
1
if [ $condition1 ] && [ $condition2 ]
2
# 等同于: if [ $condition1 -a $condition2 ]
3
# 如果condition1和condition2都为真则返回真...
4
fi;
5
6
if [[ $condition1 && $condition2 ]] # Also works.
7
# 注意&&操作不能在[ ... ]结构中使用.
8
fi;
Copied!
逻辑或||
1
if [ $condition1 ] || [ $condition2 ]
2
# 等同于: if [ $condition1 -o $condition2 ]
3
# 如果condition1和condition2有一个为真则返回真...
4
fi;
5
if [[ $condition1 || $condition2 ]] # Also works.
6
# 注意||操作不能在[ ... ]结构中使用.
7
fi;
Copied!
使用&&和||进行混合条件测试demo11。在算术计算的环境中,&&和||操作符也可以使用。
1
bash$ echo $(( 1 && 2 )) $((3 && 0)) $((4 || 0)) $((0 || 0))
2
1 0 1 0
Copied!

逗号操作符

逗号,操作符连接两个或更多的算术操作。所有的操作都被求值(可能会有副作用),但只返回最后一个操作的结构。demo5
1
let "t1 = ((5 + 3, 7 - 1, 15 - 4))"
2
echo "t1 = $t1" # t1 = 11
3
4
let "t2 = ((a = 9, 15 / 3))" # 初始化"a"并求"t2"的值.
5
echo "t2 = $t2 a = $a" # t2 = 5 a = 9
Copied!

变量

变量,是脚本编程中的如何进行数据表现的办法。它们可以在算术计算中作为操作数,在一个字符串表达式中作为符号表达抽象的意义或是其他的其它意义。变量是表示计算机内存中保存一种数据需要占的一个位置或一组的位置的标识。

变量值

如果variable1是一个变量的名字,那么$variable1就是引用这个变量的值――即这个变量它包含的数据。变量赋值与替换例子
1
t1=12
2
echo $t1
Copied!
一个未初始化的变量有一个”null”值――表示从没有被赋值过(注意null值不等于零)。在一个变量从未赋值之前就使用它通常会引起问题。然而,仍然有可能在执行算术计算时使用一个未初始化的变量。
1
echo "$uninitialized" # (blank line)
2
let "uninitialized += 5" # Add 5 to it.
3
echo "$uninitialized" # 5
4
5
# 结论:
6
# 一个未初始化的变量没有值,
7
#+ 但是似乎它在算术计算中的值是零。
8
# 这个无法证实(也可能是不可移植)的行为。
Copied!

定义变量

1
# 变量名不加美元符号
2
your_var="elaine"
3
# 重新定义
4
your_var="newname"
Copied!
注意⚠️
  1. 1.
    首个字符必须为字母(a-z,A-Z)。
  2. 2.
    中间不能有空格,可以使用下划线(_),等号左右也不能有空格。
  3. 3.
    不能使用标点符号。
  4. 4.
    不能使用bash里的关键字(可用help命令查看保留关键字)。

只读变量

1
#!/bin/bash
2
github="https://jaywcjlove.github.io"
3
readonly github
4
github="https://www.github.com"
5
6
# 运行脚本,结果如下:
7
/bin/sh: NAME: This variable is read only.
Copied!

使用变量

1
your_var="github"
2
echo $your_var
3
echo ${your_var}
4
echo "your name is ${your_var}-l"
Copied!

删除变量unset

变量被删除后不能再次使用。unset 命令不能删除只读变量。
1
myUrl="https://jaywcjlove.github.io"
2
unset myUrl
3
echo $myUrl
Copied!

变量类型

不同与许多其他的编程语言,Bash不以"类型"来区分变量。本质上来说,Bash变量是字符串,但是根据环境的不同,Bash允许变量有整数计算和比较。其中的决定因素是变量的值是不是只含有数字。
  1. 1.
    局部变量 局部变量在脚本或命令中定义,仅在当前shell实例中有效,其他shell启动的程序不能访问局部变量。
  2. 2.
    环境变量 所有的程序,包括shell启动的程序,都能访问环境变量,有些程序需要环境变量来保证其正常运行。必要的时候shell脚本也可以定义环境变量。
  3. 3.
    shell变量 shell变量是由shell程序设置的特殊变量。shell变量中有一部分是环境变量,有一部分是局部变量,这些变量保证了shell的正常运行

内部变量

内部变量
说明
$BASH
Bash二进制程序文件的路径
$BASH_ENV
该环境变量保存一个Bash启动文件路径,当启动一个脚本程序时会去读该环境变量指定的文件。
$BASH_SUBSHELL
一个指示子shell(subshell)等级的变量。它是Bash版本3新加入的。
$BASH_VERSINFO[n]
这个数组含有6个元素,指示了安装的Bash版本的信息。它和$BASH_VERSION相似,但它们还是有一些小小的不同。
$BASH_VERSION
安装在系统里的Bash版本。
$DIRSTACK
在目录堆栈里面最顶端的值(它受pushd和popd的控制)
$EDITOR
由脚本调用的默认的编辑器,一般是vi或是emacs。
$EUID
有效用户ID
$FUNCNAME
当前函数的名字
$GLOBIGNORE
由通配符(globbing)扩展的一列文件名模式。
$GROUPS
目前用户所属的组
$HOME
用户的家目录,通常是/home/username
$HOSTNAME
在系统启动时由一个初始化脚本中用hostname命令给系统指派一个名字。然而,gethostname()函数能设置Bash内部变量E$HOSTNAME。
$HOSTTYPE
机器类型,像$MACHTYPE一样标识系统硬件。
$IFS
内部字段分隔符
$IGNOREEOF
忽略EOF:在退出控制台前有多少文件结尾标识(end-of-files,control-D)会被shell忽略。
$LC_COLLATE
它常常在.bashrc或/etc/profile文件里被设置,它控制文件名扩展和模式匹配的展开顺序。
$LINENO
这个变量表示在本shell脚本中该变量出现时所在的行数。它只在脚本中它出现时有意义,它一般可用于调试。
$MACHTYPE
机器类型,识别系统的硬件类型。

位置参数

参数处理
说明
$#
传递到脚本的参数个数
$*
以一个单字符串显示所有向脚本传递的参数。如"∗"用「"」括起来的情况、以"1 2…n"的形式输出所有参数。
$
脚本运行的当前进程ID号
$!
后台运行的最后一个进程的ID号
与∗相同,但是使用时加引号,并在引号中返回每个参数。如"@"用「"」括起来的情况、以"1""2" … "$n" 的形式输出所有参数。
$-
显示Shell使用的当前选项,与set命令功能相同。
$?
显示最后命令的退出状态。0表示没有错误,其他任何值表明有错误。

参数替换

${parameter}:和$parameter是相同的,都是表示变量parameter的值,可以把变量和字符串连接。
1
your_id=${USER}-on-${HOSTNAME}
2
echo "$your_id"
3
#
4
echo "Old \$PATH = $PATH"
5
PATH=${PATH}:/opt/bin #在脚本的生存期内,能额外增加路径/opt/bin到环境变量$PATH中去.
6
echo "New \$PATH = $PATH"
Copied!
${parameter-default}, ${parameter:-default}:如果变量没有被设置,使用默认值。${parameter-default}${parameter:-default}几乎是相等的。它们之间的差别是:当一个参数已被声明,但是值是NULL的时候两者不同。
1
echo ${username-`whoami`}
2
# 如果变量$username还没有被设置,则把命令`whoami`的结果赋给该变量
Copied!
${parameter=default}, ${parameter:=default}:如果变量parameter没有设置,把它设置成默认值。除了引起的当变量被声明且值是空值时有些不同外,两种形式几乎相等。
1
echo "===== \${parameter+alt_value} ====="
2
echo
3
4
a=${param1+xyz}; echo "a = $a" # a =
5
6
param2=
7
a=${param2+xyz}
8
echo "a = $a" # a = xyz
9
10
param3=123
11
a=${param3+xyz}
12
echo "a = $a" # a = xyz
13
14
echo
15
echo "====== \${parameter:+alt_value} ======"
16
a=${param4:+xyz}
17
echo "a = $a" # a =
18
19
param5=
20
a=${param5:+xyz}
21
echo "a = $a" # a =
22
# 产生与a=${param5+xyz}不同。
23
24
param6=123
25
a=${param6+xyz}
26
echo "a = $a" # a = xyz
Copied!
${parameter?err_msg}, ${parameter:?err_msg}:如果变量parameter已经设置,则使用该值,否则打印err_msg错误信息。demo20
1
#!/bin/bash
2
# 变量替换和"usage"信息
3
4
: ${1?"Usage: $0 ARGUMENT"}
5
# 如果没有提供命令行参数则脚本在这儿就退出了,
6
#+ 并打印了错误信息.
7
# usage-message.sh: 1: Usage: usage-message.sh ARGUMENT
8
9
echo "These two lines echo only if command-line parameter given."
10
echo "command line parameter = \"$1\""
11
12
exit 0 # 仅在命令行参数提供时,才会在这儿退出.
13
14
# 分别检查有命令行参数和没有命令行参数时的退出状态。
15
# 如果有命令行参数,则"$?"为0.
16
# 否则, "$?"为1.
Copied!
${#var}:字符串长度(即变量$var的字符个数)。对于数组来说,${#array}是数组的第一个元素的升序。${#*}和${#@} 表示位置参数的个数。对于一个数组来说,${#array[*]}${#array[@]}表示数组中元素的个数。
1
E_NO_ARGS=65
2
3
if [ $# -eq 0 ] # 必须要有命令行参数给这个演示程序.
4
then
5
echo "Please invoke this script with one or more command-line arguments."
6
exit $E_NO_ARGS
7
fi
8
9
var01=abcdEFGH28ij
10
echo "var01 = ${var01}"
11
echo "Length of var01 = ${#var01}"
12
# 现在,让我们在变量值内嵌入一个空格.
13
var02="abcd EFGH28ij"
14
echo "var02 = ${var02}"
15
echo "Length of var02 = ${#var02}"
16
17
echo "Number of command-line arguments passed to script = ${#@}"
18
echo "Number of command-line arguments passed to script = ${#*}"
19
20
exit 0
Copied!
${var#Pattern}, ${var##Pattern}:删除从$var前端开始的最短或最长匹配$Pattern的字符串。demo22
1
echo `basename $PWD` # 当前工作目录的基本名字.
2
echo "${PWD##*/}" # 当前工作目录的基本名字.
3
echo
4
echo `basename $0` # 脚本名.
5
echo $0 # 脚本名.
6
echo "${0##*/}" # 脚本名.
7
echo
8
filename=test.data
9
echo "${filename##*.}" # data
10
# 文件的扩展名.
Copied!

declare/typeset

declare或typeset内建命令(它们是完全相同的)可以用来限定变量的属性。这是在某些编程语言中使用的定义类型不严格的方式。
  • 命令declare是bash版本2之后才有的。
  • 命令typeset也可以在ksh脚本中运行。
选项
参数
说明
例子
-r
只读
declare -r var1
-i
整数
declare -i number;number=3;
-a
数组
declare -a indices
-f
函数
declare -f 会列出所有在此脚本前面已定义的函数
-x export
函这样将声明一个变量作为脚本的环境变量而被导出。
declare -x var3
-x var=$value
declare命令允许在声明变量类型的时候同时给变量赋值。
declare -x var3=373

变量间接引用

假设一个变量的值是第二个变量的名字。这样要如何才能从第一个变量处重新获得第二个变量的值?例如,a=letter_of_alphabetletter_of_alphabet=z,是否能由a引用得到z ? 这确实可以办到,这种技术被称为间接引用。
1
a=letter_of_alphabet # 变量"a"保存着另外一个变量的名字.
2
letter_of_alphabet=z
3
# 直接引用.
4
echo "a = $a" # a = letter_of_alphabet
5
6
# 间接引用.
7
eval a=\$a
8
echo "Now a = $a" # 现在 a = z
9
exit 0
Copied!

$RANDOM

$RANDOM是Bash的一个返回伪随机整数(范围为0 - 32767)的内部函数(而不是一个常量或变量),它不应该用于产生加密的密钥。demo25
1
# 模拟掷骰子.
2
SPOTS=6 # 模除 6 会产生 0 - 5 之间的值.
3
# 结果增1会产生 1 - 6 之间的值.
4
# 多谢Paulo Marcel Coelho Aragao的简化.
5
die1=0
6
die2=0
7
# 这会比仅设置SPOTS=7且不增1好?为什么会好?为什么会不好?
8
9
# 单独地掷每个骰子,然后计算出正确的机率.
10
11
let "die1 = $RANDOM % $SPOTS +1" # 掷第一个.
12
let "die2 = $RANDOM % $SPOTS +1" # 掷第二个.
13
# 上面的算术式中,哪个操作符优先计算 --
14
#+ 取模 (%) 还是 加法 (+)?
15
16
let "throw = $die1 + $die2"
17
echo "Throw of the dice = $throw"
18
19
exit 0
Copied!

双括号结构

((...))结构来使用C风格操作符来处理变量。demo26
1
(( a = 23 )) # 以C风格来设置一个值,在"="两边可以有空格.
2
echo "a (initial value) = $a"
3
4
(( a++ )) # C风格的计算后自增.
5
echo "a (after a++) = $a"
6
7
(( a-- )) # C风格的计算后自减.
8
echo "a (after a--) = $a"
9
10
11
(( ++a )) # C风格的计算前自增.
12
echo "a (after ++a) = $a"
13
14
(( --a )) # C风格的计算前自减.
15
echo "a (after --a) = $a"
Copied!

转义字符

在单个字符前面的转义符\告诉shell不必特殊解释这个字符,只把它当成字面上的意思。但在一些命令和软件包里,比如说echo和sed,转义一个字符可能会引起一个相反的效果--因为它们可能触发那个字符的特殊意思。demo13
\r 回车 \n 换行 \c 不换行 \t 水平制表符 \v 垂直制表符 \a 表示“警告”(蜂鸣或是闪动) \\ 反斜杠字符 \0ddd 将自负表示成1到3的八进制数值

退出/退出状态

$? 变量用于测试脚本中的命令执行结果非常的有用。demo14
1
echo hello
2
echo $? # 因为上一条命令执行成功,打印0。
3
4
lskdf # 无效命令。
5
echo $? # 因为上面的无效命令执行失败,打印一个非零的值。
6
7
exit 113 # 返回113状态码给shell。
8
# 可以运行脚本结束后立即执行命令"echo $?" 检验。
9
10
# 依照惯例,命令'exit 0'表示执行成功,
11
#+ 当产生一个非零退出值时表示一个错误或是反常的条件。
Copied!
下面这些退出状态码,用于保留(reserved meanings) 的含义,不应该在用户脚本使用。
Exit Code Number
Meaning
Example
Comments
1
Catchall for general errors
let "var1 = 1/0"
Miscellaneous errors, such as "divide by zero"
2
Misuse of shell builtins (according to Bash documentation)
-
Seldom seen, usually defaults to exit code 1
126
Command invoked cannot execute
-
Permission problem or command is not an executable
127
"command not found"
-
Possible problem with $PATH or a typo
128
Invalid argument to exit
exit 3.14159
exit takes only integer args in the range 0 - 255 (see footnote)
128+n
Fatal error signal "n"
kill -9 $PPID
of script $? returns 137 (128 + 9)
130
Script terminated by Control-C
-
Control-C is fatal error signal 2, (130 = 128 + 2, see above)
255*
Exit status out of range
exit -1
exit takes only integer args in the range 0 - 255

测试

测试结构

一个if/then结构能包含嵌套的比较和测试。
1
echo "Testing \"false\""
2
if [ "false" ] # "false"是一个字符串.
3
then
4
echo "\"false\" is true." #+ 它被测试为真.
5
else
6
echo "\"false\" is false."
7
fi # "false"为真.
Copied!
Else if 和 elif
elif是else if的缩写。作用是在一个if/then里嵌入一个内部的if/then结构。
[[]]结构比Bash版本的[]更通用。用[[ ... ]]测试结构比用[ ... ]更能防止脚本里的许多逻辑错误。比如说,&&,||,<>操作符能在一个[[]]测试里通过,但在[]结构会发生错误。
(( ))结构扩展并计算一个算术表达式的值。如果表达式值为0,会返回1或假作为退出状态码。一个非零值的表达式返回一个0或真作为退出状态码。这个结构和先前test命令及[]结构的讨论刚好相反。

文件测试操作符

如果下面的条件成立返回真。demo15
操作符
描述
-e
文件存在
-a
文件存在,这个和-e的作用一样. 它是不赞成使用的,所以它的用处不大。
-f
文件是一个普通文件(不是一个目录或是一个设备文件)
-s
文件大小不为零
-d
文件是一个目录
-b
文件是一个块设备(软盘,光驱,等等。)
-c
文件是一个字符设备(键盘,调制解调器,声卡,等等。)
-p
文件是一个管道
-h
文件是一个符号链接
-L
文件是一个符号链接
-S
文件是一个socket
-t
文件(描述符)与一个终端设备相关。
-r
文件是否可读 (指运行这个测试命令的用户的读权限)
-w
文件是否可写 (指运行这个测试命令的用户的读权限)
-x
文件是否可执行 (指运行这个测试命令的用户的读权限)
-g
文件或目录的设置-组-ID(sgid)标记被设置。
-u
文件的设置-用户-ID(suid)标志被设置
-k
粘住位设置
-N
文件最后一次读后被修改
注意⚠️
  1. 1.
    -t 这个测试选项可以用于检查脚本中是否标准输入 ([ -t 0 ])或标准输出([ -t 1 ])是一个终端。
  2. 2.
    -g 如果一个目录的sgid标志被设置,在这个目录下创建的文件都属于拥有此目录的用户组,而不必是创建文件的用户所属的组。这个特性对在一个工作组里的同享目录很有用处。

比较操作符

二元比较操作符比较两个变量或是数值。注意整数和字符串比较的分别。
整数比较
demo16
比较操作符
描述
例子
-eq
等于
if [ "$a" -eq "$b" ]
-ne
不等于
if [ "$a" -ne "$b" ]
-gt
大于
if [ "$a" -gt "$b" ]
-ge
大于等于
if [ "$a" -ge "$b" ]
-lt
小于
if [ "$a" -lt "$b" ]
-le
小于等于
if [ "$a" -le "$b" ]
<
小于(在双括号里使用)
(("$a" < "$b"))
<=
小于等于 (在双括号里使用)
(("$a" <= "$b"))
>
大于 (在双括号里使用)
(("$a" > "$b"))
>=
大于等于(在双括号里使用)
(("$a" >= "$b"))
字符串比较
比较操作符
描述
例子
=
等于
if [ "$a" = "$b" ]
==
等于,它和=是同义词。
if [ "$a" == "$b" ]
!=
不相等,操作符在[[ ... ]]结构里使用模式匹配.
if [ "$a" != "$b" ]
<
小于,依照ASCII字符排列顺序,注意"<"字符在[ ] 结构里需要转义
if [[ "$a" < "$b" ]] if [ "$a" \< "$b" ]
>
大于,依照ASCII字符排列顺序,注意">"字符在[ ] 结构里需要转义.
if [[ "$a" > "$b" ]] if [ "$a" \> "$b" ]
-z
字符串为"null",即是指字符串长度为零。
-
-n
字符串不为"null",即长度不为零。
-
混合比较
比较操作符
描述
例子
-a
逻辑与,如果exp1和exp2都为真,则exp1 -a exp2返回真。
if [ "$exp1" -a "$exp2" ]
-o
逻辑或,只要exp1和exp2任何一个为真,则exp1 -o exp2 返回真。
if [ "$exp1" -o "$exp2" ]
在一个混合测试中,把一个字符串变量引号引起来可能还不够。如果$string变量是空的话,表达式[ -n "$string" -o "$a" = "$b" ]在一些Bash版本中可能会引起错误。安全的办法是附加一个外部的字符串给可能有空字符串变量比较的所有变量,[ "x$string" != x -o "x$a" = "x$b" ] (x字符可以互相抵消)

操作字符串

Bash已经支持了令人惊讶的字符串操作的数量。不一致的命令语法和冗余的功能,导致真的学起来有困难。

字符串长度

1
String=abcABC123ABCabc
2
3
echo ${#String} # 15
4
echo `expr length $String` # 15
5
echo `expr "$String" : '.*'` # 15
Copied!
匹配字符串开头的字串的长度,下面两种方法的 $substring 是一个正则表达式。
expr match "$string" '$substring' expr "$string" : '$substring'
1
String=abcABC123ABCabc
2
# └------┘
3
#
4
echo `expr match "$String" 'abc[A-Z]*.2'` # 8
5
echo `expr "$String" : 'abc[A-Z]*.2'` # 8
Copied!

索引

expr index $string $substring 在字符串$string中$substring第一次出现的数字位置
1
String=abcABC123ABCabc
2
echo `expr index "$String" C12` # 6
3
# C 字符的位置.
4
5
echo `expr index "$String" 1c` # 3
6
# 'c' (in #3 position) matches before '1'.
Copied!

字串提取

${string:position} 把$string中从第$postion个字符开始字符串提取出来。如果$string是"*"或"@",则表示从位置参数中提取第$postion后面的字符串。 ${string:position:length} 把$string中$postion个字符后面的长度为$length的字符串提取出来。demo18
1
# 字串提取
2
String=abcABC123ABCabc
3
# 0123456789.....
4
# 以0开始计算.
5
6
echo ${String:0} # abcABC123ABCabc
7
echo ${String:1} # bcABC123ABCabc
8
echo ${String:7} # 23ABCabc
9
echo ${String:7:3} # 23A
10
# 提取的字串长为3
11
12
# 有没有可能从字符串的右边结尾处提取?
13
14
echo ${String:-4} # abcABC123ABCabc
15
# 默认是整个字符串,就相当于${parameter:-default}.
16
# 然而. . .
17
18
echo ${String:(-4)} # Cabc
19
echo ${String: -4} # Cabc
20
# 这样,它可以工作了.
21
# 圆括号或附加的空白字符可以转义$position参数.
Copied!

字串移动

${string#substring}从$string左边开始,剥去最短匹配$substring子串。 ${string##substring}从$string左边开始,剥去最长匹配$substring子串。 ${string%substring} 从$string结尾开始,剥去最短匹配$substring子串。 ${string%%substring}从$string结尾开始,剥去最长匹配$substring子串。
1
String=abcABC123ABCabc
2
# ├----┘ ┆
3
# └----------┘
4
5
echo ${String#a*C} # 123ABCabc
6
# 剥去匹配'a'到'C'之间最短的字符串.
7
8
echo ${String##a*C} # abc
9
# 剥去匹配'a'到'C'之间最长的字符串.
10
11
12
String=abcABC123ABCabc
13
# ┆ ||
14
# └------------┘
15
16
echo ${String%b*c} # abcABC123ABCa
17
# 从$String后面尾部开始,剥去匹配'a'到'C'之间最短的字符串.
18
19
echo ${String%%b*c} # a
20
# 从$String后面尾部开始,剥去匹配'a'到'C'之间最长的字符串.
Copied!

用awk处理字符串

Bash脚本可以调用awk的字符串操作功能来代替它自己内建的字符串操作符
1
String=23skidoo1
2
# 012345678 Bash
3
# 123456789 awk
4
# 注意上面两个程序对索引的不同处理:
5
# Bash把字符串的第一个字符的标号称为'0'。
6
# Awk把字符串的第一个字符的标号称为'1'。
7
8
echo ${String:2:4} # position 3 (0-1-2), 4 characters long
9
# skid
10
11
# 在awk中与Bash的${string:pos:length}等同的是substr(string,pos,length)。
12
echo | awk '{
13
print substr("'"${String}"'",3,4) # skid
14
}'
15
# 用一个空的"echo"由管道传一个空的输入给awk,
16
#+ 这样就不必提供一个文件名给awk。
17
exit 0
Copied!

for/while

重复一些命令的代码块,如果条件不满足就退出循环。

for

在循环的每次执行中,arg将顺序的存取list中列出的变量,下面是一个基本的循环结构。demo27
for arg in [list] do command(s)... done
每个[list]中的元素都可能包含多个参数,在处理参数组时,这是非常有用的,使用set命令来强制解析每个[list]中的元素。并且分配每个解析出来的部分到一个位置参数中。
循环的一个简单例子
1
for planet in Mercury Venus Earth Mars Jupiter Saturn Uranus Neptune Pluto
2
do
3
echo $planet # 每个行星被单独打印在一行上.
4
done
Copied!

while

一个while循环可以有多个判断条件,但是只有最后一个才能决定是否退出循环。然而这需要一种有点不同的循环语法。
while [condition] do command... done
1
# --------------------------
2
# 简单的while循环
3
# --------------------------
4
var0=0
5
LIMIT=10
6
7
while [ "$var0" -lt "$LIMIT" ]
8
do
9
echo -n "$var0 " # -n 将会阻止产生新行。
10
# ^ 空格,数字之间的分隔。
11
var0=`expr $var0 + 1` # var0=$(($var0+1)) 也可以。
12
# var0=$((var0 + 1)) 也可以。
13
# let "var0 += 1" 也可以。
14
done # 使用其他的方法也行。
15
# --------------------------
16
# 多条件的while循环
17
# --------------------------
18
var1=unset
19
previous=$var1
20
21
while echo "previous-variable = $previous"
22
echo
23
previous=$var1
24
[ "$var1" != end ] # 记录之前的$var1.
25
# 这个"while"循环中有4个条件, 但是只有最后一个能控制循环.
26
# 退出状态由第4个条件决定.
27
do
28
echo "Input variable #1 (end to exit) "
29
read var1
30
echo "variable #1 = $var1"
31
done
32
exit 0
Copied!

until

这个结构在循环的顶部判断条件,并且如果条件一直为false那就一直循环下去。(与while相反)。
until [condition-is-true] do command... done
注意⚠️
  1. 1.
    until循环的判断在循环的顶部,这与某些编程语言是不同的。
  2. 2.
    与for循环一样,如果想把do和条件放在一行里,就使用";"。
until [condition-is-true] ; do
1
END_CONDITION=end
2
until [ "$var1" = "$END_CONDITION" ]
3
# 在循环的顶部判断条件.
4
do
5
echo "Input variable #1 "
6
echo "($END_CONDITION to exit)"