注意,本文环境为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脚本里没有多行注释,只能每一行加一个#号。
基本命令
- echo "some text": 将文字内容打印在屏幕上
- ls: 显示文件列表
- wc –l filewc -w filewc -c file: 计算文件行数计算文件中的单词数计算文件中的字符数
- cp sourcefile destfile: 文件拷贝
- mv oldname newname : 重命名文件或移动文件
- rm file: 删除文件
- grep 'pattern' file: 在文件内搜索字符串比如:grep 'searchstring' file.txt
- cut -b colnum file: 指定欲显示的文件内容范围,并将它们输出到标准输出设备比如:输出每行第5个到第9个字符cut -b5-9 file.txt千万不要和cat命令混淆,这是两个完全不同的命令
- cat file.txt: 输出文件内容到标准输出设备(屏幕)上
- file somefile: 得到文件类型
- read var: 提示用户输入,并将输入赋值给变量
- sort file.txt: 对file.txt文件中的行进行排序
- uniq: 删除文本文件中出现的行列比如:
sort file.txt | uniq
- expr: 进行数学运算Example: add 2 and 3
expr 2 "+" 3
- find: 搜索文件比如:根据文件名搜索find . -name filename -print
- tee: 将数据输出到标准输出设备(屏幕) 和文件比如:
somecommand | tee outfile
- basename file: 返回不包含路径的文件名比如: basename /bin/tux将返回 tux
- dirname file: 返回文件所在路径比如:dirname /bin/tux将返回 /bin
- head file: 打印文本文件开头几行
- tail file : 打印文本文件末尾几行
- sed: Sed是一个基本的查找替换程序。可以从标准输入(比如命令管道)读入文本,并将结果输出到标准输出(屏幕)。该命令采用正则表达式(见参考)进行搜索。不要和shell中的通配符相混淆。比如:将linuxfocus 替换为
LinuxFocus :cat text.file | sed 's/linuxfocus/LinuxFocus/' > newtext.file
- awk: awk 用来从文本文件中提取字段。缺省地,字段分割符是空格,可以使用-F指定其他分割符。
cat file.txt | awk -F, '{print $1 "," $3 }'
这里我们使用,作为字段分割符,同时打印第一个和第三个字段。如果该文件内容如下:Adam Bor, 34, IndiaKerry Miller, 22, USA
命令输出结果为:Adam Bor, IndiaKerry Miller, USA
管道, 重定向和 backtick
- 管道 (|) 将一个命令的输出作为另外一个命令的输入。
grep "hello" file.txt | wc -l
在file.txt中搜索包含有”hello”的行并计算其行数。 在这里grep命令的输出作为wc命令的输入。当然您可以使用多个命令。
- 重定向: 大多数 UNIX 系统命令从你的终端接受输入并将所产生的输出发送回到您的终端。一个命令通常从一个叫标准输入的地方读取输入,默认情况下,这恰好是你的终端。同样,一个命令通常将其输出写入到标准输出,默认情况下,这也是你的终端。
命令 | 说明 |
---|---|
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 之间的内容作为输入。 |
- 反短斜线 使用反短斜线可以将一个命令的输出作为另外一个命令的一个命令行参数。 比如
find . -mtime -1 -type f -print
,用来查找过去24小时(-mtime –2则表示过去48小时)内修改过的文件。如果您想将所有查找到的文件打一个包,则可以使用以下脚本:
#!/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