编写shell脚本的一些简单概念

注意,本文环境为macOS Sierra 10.12.5 + zsh

开始

脚本的第一行需要指定解释器。

#!/bin/bash

#! 是一个约定的标记,它告诉系统这个脚本需要什么解释器来执行,即使用哪一种 Shell。我们在运行shell脚本时可以直接运行解释器,将shell脚本的文件名作为参数,那么这第一行的指定解释器就没有用了。

/bin/sh test.sh
#文件的后缀名可以任意修改,不影响运行
/bin/php test.php

但是如果直接作为程序运行的话,首先需要修改文件的权限,然后cd到相应目录:

chmod +x ./test.sh  #使脚本具有执行权限
./test.sh  #执行脚本

注意一定要写成 ./test.sh,而不是 test.sh。运行其它二进制的程序也一样,直接写 test.sh,linux 系统会去 PATH 里寻找有没有叫 test.sh 的,而只有 /bin, /sbin, /usr/bin,/usr/sbin 等在 PATH 里,你的当前目录通常不在 PATH 里,所以写成 test.sh 是会找不到命令的,要用 ./test.sh 告诉系统说,就在当前目录找。

关于文件权限

Linux/Unix 的档案调用权限分为三级 : 档案拥有者、群组、其他。利用 chmod 可以藉以控制文件如何被他人所调用。 参数 : mode : 权限设定字串,格式如下 : [ugoa...][[+-=][rwxX]...][,...],其中:

u 表示该档案的拥有者,g 表示与该档案的拥有者属于同一个群体(group)者,o 表示其他以外的人,a 表示这三者皆是,即user/group/others。

+ 表示增加权限、- 表示取消权限、= 表示唯一设定权限。

r(readable) 表示可读取,w(writable) 表示可写入,x(excutable) 表示可执行,X 表示只有当该档案是个子目录或者该档案已经被设定过为可执行。

详细的使用可以自行搜索。

变量

直接定义变量,不需要关键词声明,注意不需要添加多余的空格,否则无法识别:

a = "hello world" #无法识别
a="hello world"

命名规则很简单:首个字符为大小写字母;中间可以有下划线;不能有空格,标点;不能使用关键词。

使用变量时,需要添加$符号,大括号可选,来帮助解释器识别边界:

num=2
echo "this is the $numnd" #this is the
echo "this is the ${num}nd" #this is the 2nd

readonly 可以使变量定义为只读变量,无法再被赋值; unset 可以删除一个变量,但不能删除只读变量

hi="hello world"
readonly hi
hi="hi again" #This variable is read only.

n=1
unset n
echo $n #nothing

字符串

字符串可以用单引号,也可以用双引号,也可以不用引号。但注意单双引号是有区别的:

单引号里的任何字符都会原样输出,单引号字符串中的变量是无效的; 单引号字串中不能出现单引号(对单引号使用转义符后也不行); 双引号里可以有变量; 双引号里可以出现转义字符;

用起来类似ES6中的模版字符串:

your_name="somebody"
greeting="hello ${your_name}"
echo ${greeting} #hello somebody

字符串长度:变量名前加#号来获取

string="abcd"
echo ${#string} #输出 4

提取子字符串:有一点点形似python切片,string:起始位:字符个数,Linux的字符串截取方式还有很多,具体可以另行搜索。

string="but what is python?"
echo ${string:4:4} # what

数组

用括号来表示数组,数组元素用"空格"符号分割开。定义数组的一般形式为 数组名=(值1 值2 ... 值n),以下标来获取单个值,使用@符号可以获取数组中的所有元素,以#号获取长度:

arr1=(value0 value1 value2 value3)

arr2=(
  val1
  val2
  val3
  val456789
)

echo ${arr1[0]} # val1
echo ${arr1[@]} # val1 val2 val3 val4
echo ${#arr1[@]} # 4
echo ${#arr1[3]} # 9

注释

注释以#号开头,shell脚本里没有多行注释,只能每一行加一个#号。

基本命令

管道, 重定向和 backtick

grep "hello" file.txt | wc -l

在file.txt中搜索包含有”hello”的行并计算其行数。 在这里grep命令的输出作为wc命令的输入。当然您可以使用多个命令。

命令说明
command > file将输出重定向到 file。
command < file将输入重定向到 file。
command >> file将输出以追加的方式重定向到 file。
n > file将文件描述符为 n 的文件重定向到 file。
n >> file将文件描述符为 n 的文件以追加的方式重定向到 file。
n >& m将输出文件 m 和 n 合并。
n <& m将输入文件 m 和 n 合并。
<< tag将开始标记 tag 和结束标记 tag 之间的内容作为输入。
#!/bin/sh 
# The ticks are backticks (`) not normal quotes ('): 
tar -zcvf lastmod.tar.gz `find . -mtime -1 -type f -print` 

流程控制

关于条件和循环,有一点要注意的是shell脚本中流程不可为空:

a=10
b=20
if [ $a == $b ]
then
  echo "a 等于 b"
elif [ $a -gt $b ]
then
  echo "a 大于 b"  #如果没有要执行的就干脆不要else,不可为空
elif [ $a -lt $b ]
then
  echo "a 小于 b"
else
   echo "没有符合的条件"
fi

for循环和while循环:

for loop in 1 2 3 4 5
do
    echo "The value is: $loop"
done

# The value is: 1
# The value is: 2
# The value is: 3
# The value is: 4
# The value is: 5

int=1
while(( $int<=5 ))
do
        echo $int
        let "int++"
done

until循环与while循环在处理方式上刚好相反。一般while循环优于until循环,但在某些时候—也只是极少数情况下,until循环更加有用。

until condition
do
    command
done

case取值后面必须为单词in,每一模式必须以右括号结束。取值可以为变量或常数。匹配发现取值符合某一模式后,其间所有命令开始执行直至 ;;。 取值将检测匹配的每一个模式。一旦模式匹配,则执行完匹配模式相应命令后不再继续其他模式。如果无一匹配模式,使用星号 * 捕获该值,再执行后面的命令。 下面的脚本提示输入1到4,与每一种模式进行匹配:

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 #case反过来作为结束语

跳出循环使用break和continue,同JS中一样。

函数

定义函数和JS也很像,但function关键词是可以省略的,调用时也不需要圆括号:

demo1(){
    echo "这是我的第一个 shell 函数!"
}

function demo2(){
  echo "and this is the 2nd!"
}
echo "-----函数开始执行-----"
demo1
demo2
echo "-----函数执行完毕-----"

# 输出
# -----函数开始执行-----
# 这是我的第一个 shell 函数!
# and this is the 2nd!
# -----函数执行完毕-----

所有函数在使用前必须定义。这意味着必须将函数放在脚本开始部分,直至shell解释器首次发现它时,才可以使用。调用函数仅使用其函数名即可,如果有参数的话以空格隔开,函数返回值在调用该函数后通过 $? 来获得。在函数体内部,通过 $n 的形式来获取参数的值,例如,$1表示第一个参数,$2表示第二个参数...但第10个以后需要加上大括号,比如${10}。

另外,还有几个特殊字符用来处理参数:

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

引用外部文件

和其他语言一样,Shell 也可以包含外部脚本。这样可以很方便的封装一些公用的代码作为一个独立的文件。被包含的文件不需要可执行权限。 Shell 文件包含的语法格式如下:

. filename   # 注意点号(.)和文件名中间有一空格
# 或
source filename