1. shell脚本文件
2. shell脚本的局限性
3. shell脚本执行说明
4. 引号与字面量
   4.1 单引号的字面义
   4.2 双引号内可以使用单引号。
   4.3 倒引号(左上角Tab键上面那个键)
5. 特殊变量
   5.1 能删除第一个参数的shift命令
   5.2 特殊符号
   5.3 退出码
6. 条件判断
   6.1 使用其他命令来测试,grep
   6.2 多条件分支 elif
   6.3 多条件分支 case
   6.4 逻辑结构: &&(和); ||(或)
   6.5 测试条件
   6.6 test运算符可分为三类 man test(1)
      6.6.1 一元运算符,之要求一个参数,比如前面的-f
      6.6.2 二元运算符
      6.6.3 字符串测试
      6.6.4 算术测试
      6.6.5 case字符串匹配
7. for循环
8. while循环
9. 命令替换
10. 管理临时文件
11. here文档
12. 重要的shell脚本工具
   12.1 去掉扩展名
   12.2 grep,awk,sed等模式查找,分析,编辑工具
   12.3 xargs
   12.4 expr
   12.5 exec 命令是shell内置的,他会用其后的程序的进程来取代当前的shell进程。
   12.6 子shell
   12.7 在脚本中包含其他文件(.)
   12.8 读取用户输入read
13. shell例子
14. 部分工具帮助
   14.1 xargs
   14.2 expr
15. 更多学习相关链接


shell脚本笔记
Bourne shell脚本,就是将一系列命令写在一个文件当中,然后让shell从该文件读取命令,就像从终端读取一样。

1. shell脚本文件

 

  1. 使用.sh作为后缀的脚本文件,很多文本编辑器默认显示为彩色编码模式。
  2. #!/bin/sh # 指定脚本解释器,这里是用/bin/sh做解释器的
  3. # 注释符号。
  4. 运行方式1: (直接运行) $ bash sh02.sh 或者 $ sh sh02.sh
  5. 运行方式2: (作为可执行文件)可执行权限: chmod +rx test.sh, 或者 chmod 700 test.sh 等给脚本执行和读取(5 or 7)的权限。然后运行脚本 $ ./test.sh


2. shell脚本的局限性

shell脚本的优点是能使任务简单化,自动化,而不用在命令行提示符下一条条的敲命令。对于批量处理文件很方便。特别适合处理一些系统管理方面的小问题.类似Windows的批处理文件(*.bat)

但是如果哦要分解字符串,做繁复的数学计算,复杂的数据库交互,或者想写函数及复杂的控制结构,最好使用Python, Perl之类的脚本语言,或者awk,甚至C这样的编译型语言。

3. shell脚本执行说明

在执行之前,shell会查找其中的变量,通配符以及其他代词,如果有的话,就将他们进行替代。
将替换后的结果返回给命令。

4. 引号与字面量

引号通常用于创建字面量,即原封不动的字面义。
单引号保证shell不会做任何替换。如下面的单引号。
echo $100
$ ./sh01
00
$ ./sh01 hh
hh00
echo "$100"
$ ./sh01
00
echo '$100'
$ ./sh01
$100
 echo 'There is no * in mp path: $PATH'
There is no * in mp path: $PATH
双引号与单引号差不多,只是shell会对双引号中所有变量都进行扩展。如下面的双引号,$PATH会被替换,*号不变。
$ echo "There is no * in mp path: $PATH"
There is no * in mp path: /usr/local/bin:/usr/bin:/bin:/usr/local/sbin:/usr/bin/site_perl:/usr/bin/vendor_perl:/usr/bin/core_perl

4.1 单引号的字面义

 echo I don\'t like.
I don't like.
反斜和单引号不能被任何单引号对包裹。
$ echo 'I don\'t like.'//错误语法

4.2 双引号内可以使用单引号。

echo "I don't like."
I don't like.
'不做转换'的一般做法:
1. 将所有'(单引号)改成'\''(单引号,反斜,单引号,单引号);
2. 用单引号对'包裹整个'字符串.
 echo 'I don'\''t like.'
I don't like.

4.3 倒引号(左上角Tab键上面那个键)

由倒引号括起来的字符串被shell解释为命令行,在执行时,shell会先执行该命令,并以他的标准输出结果取代整个引号部分。

5. 特殊变量

单个参数: $1, $2, ...
正整数命名的变量,都包含了脚本的参数值。
比如脚本文件 sh01:
#!/bin/sh
echo First argument: $1
echo First argument: $3
$ sudo chmod 755 sh01
$ ./sh01 01 02 03
First argument: 01
First argument: 03

5.1 能删除第一个参数的shift命令

脚本文件sh01的内容执行sh01脚本
比如: a001

5.2 特殊符号

单个参数: $1, $2, ...
删除第一个参数的命令: shift
参数的数量: $#
所有参数: $@
分割长行用反斜: \
脚本名: $0

标准错误重定向到标准输出: 2>&1
标准输出重定向到标准错误: 1>&2

5.3 退出码

进程号: $$
退出码: $?
$ echo $?
0
通常0表示正常退出.
$ ls /psddsdf
ls: cannot access '/psddsdf': No such file or directory
$ echo $?
2
$ echo $?
0
通常非0表示非正常退出。如上例的2。
连续使用2次echo $?, 第二次总是正常退出0.
指定退出码: exit 1
特例,diff,grep正常退出时也会用到非0码。
grep会在匹配时返回0, 不匹配时返回1.
grep和diff使用2来代表出错。
特别的退出码信息可以在命令的帮助信息里查询,通常在EXIT VALUE或DIAGNOSTICS小节中。

6. 条件判断

if/then/ else和case语句
比如: a003

'['字符是Unix系统中一个实际存在的命令,而不是shell的语法。他能进行条件测试,也叫test。
对比'['和'test',会发现他们指向同一个inode。或者其中一个是另一个的符号链接。

条件判断的执行说明
条件判断开始与if;
shell执行if关键字后的命令,获取其推出码;
如果退出码是0,shell就会接着执行then关键字后的命令;直至遇到else或fi关键字;
如果退出码非0,并且有else,就会执行else后面的命令;
条件判断至与fi。

防范空参数
if [ "$1" = hi ]; then
if [ x"$1" = x"hi" ]; then

if后面必须是命令,如果想将then放在同一行,必须在test命令后面加上分号(;).如果没有分号,text命令会将then当成参数。
不想用分号,可以换行输入then。

6.1 使用其他命令来测试,grep

比如: a005

6.2 多条件分支 elif

比如: a007

6.3 多条件分支 case

 

6.4 逻辑结构: &&(和); ||(或)

cmd1 && cmd2
执行cmd1到退出码为0,接着执行cmd2.
cmd1 || cmd2
执行cmd1到退出码为非0,接着执行cmd2.

if cmd1 && cmd2
若执行cmd1到退出码为0,会接着执行cmd2. if取cmd2的退出码作为判断结果;
若执行cmd1到退出码为非0,if直接取这个非0值作为判断结果;不需要在执行cmd2.
if cmd1 || cmd2
若执行cmd1到退出码为0,if直接取这个0值作为判断结果;不需要在执行cmd2.
执行cmd1到退出码为0,会接着执行cmd2. if取cmd2的退出码作为判断结果;

如果条件判断包含了text([)命令,可以使用-a, -o代替&&, ||.

6.5 测试条件

比如: a009

6.6 test运算符可分为三类 man test(1)

文件测试
字符串测试
算术测试

6.6.1 一元运算符,之要求一个参数,比如前面的-f

-e: 文件存在时返回 true
-s: 文件非空时返回 true
文件类型运算符
-f: 普通文件
-d: 目录
-h: 符号链接
-b: 块设备
-c: 字符设备
-p: 命名管道
-s: 套接字

test命令会直达符号链接所指的文件(除非用-h测试)。
即,如果哦link是某个普通文件的符号链接,则[-f link]返回true (退出码为0)

文件权限运算符
-r: 可读
-w: 可写
-x: 可执行
-u: Setuid
-g: Setgid
-k: "Sticky"

6.6.2 二元运算符

-nt: a新于b 返回true
-ot: a旧与b 返回true
-ef: a是否是b的硬链接

6.6.3 字符串测试

=:  2个字符串相同 返回true
!=: 2个字符串不同 返回true
-z: 参数为空 返回true
-n: 参数非空 返回true

6.6.4 算术测试

=: 测试字符串的,不是数字!
-eq: 相等
-ne: 不等
-lt: 更小
-gt: 更大
-le: 更小或相等
-ge: 更大或相等

6.6.5 case字符串匹配

case条件判断不执行任何test命令,因此没有退出码。它是做模式匹配的。
a011
遇到匹配的就会运行后面的命令,直到;;时,跳到esac关键字。

7. for循环

比如: a013

8. while循环

会用到退出码,非0就退出。
比如: a015

进行grep -q firstline测试。只要该测试的退出码非0(本例中,若$FILE的最后10行不包含字符串firstline,则非0),循环就会结束。

可以用break语句打断while循环。
还有类似的until循环,退出码为0.
建议:应该用awk或Python来代替while。

9. 命令替换

用$()将命令包围,使该命令的输出被当作变量来使用。
比如: a017
grep的结果发送到sed;
sed将匹配.*:的内容清空掉,再发送给head;

不要过度使用命令替换
比如在脚本中不要使用$(ls), 而是改用shell展开*, 因为这样更快捷。

命令替换的传统做法是使用反引号(‘’)包围命令.
$()语法是一种较新的形式,且符合POSIX标准,更容易阅读和编写。

10. 管理临时文件

比如: a019

11. here文档

用于打印大段文字,类似html的<pre>标签。注,here文档中的shell变量都会被展开。
比如: a021

12. 重要的shell脚本工具

basename等很少单独使用,通常出现在脚本里。

12.1 去掉扩展名

$ basename example.html .html

去掉目录部分
$ basename /usr/local/bin/example

gif转换为png
比如: a023

12.2 grep,awk,sed等模式查找,分析,编辑工具

 

12.3 xargs

从标准输入构建和执行命令行.
本手册页记录了xargs的GNU版本。
xargs从标准输入中读取项目,由空格分隔(可以使用双引号或单引号或反斜杠保护)或换行符,并使用任何初始参数执行命令(默认为/bin/echo)一次或多次从标准输入读取的项目。
标准输入上的空行将被忽略。

构建命令的命令行直到达到系统定义的限制(除非使用-n和-L选项)。
将根据需要多次调用指定的命令以使用输入项列表。
通常,命令的调用次数将少于输入中的项目。
这通常会带来显着的性能优势。
有些命令也可以并行执行;请参阅-P选项。

因为Unix文件名可以包含空格和换行符,所以这种默认行为通常是有问题的;包含空格和/或换行符的文件名由xargs错误处理。
在这些情况下,最好使用-0选项,以防止此类问题。
使用此选项时,您需要确保为xargs生成输入的程序也使用空字符作为分隔符。
例如,如果该程序是GNU find,则-print0选项会为您执行此操作。

如果命令的任何调用以状态255退出,则xargs将立即停止而不读取任何进一步的输入。
发生这种情况时,会在stderr上发出错误消息。

$ xargs --show-limits
您的环境变量占用2600个字节
参数长度的POSIX上限(本系统):2092504
参数长度的POSIX最小允许上限(所有系统):4096
我们实际可以使用的最大命令长度:2089904
我们实际使用的命令缓冲区大小:131072
最大并行度( -  max-procs必须不大):2147483647

xargs的执行现在将继续,它将尝试读取其输入和运行命令; 如果这不是您想要的,请输入文件结束键击。
警告:echo至少运行一次。 如果您不希望发生这种情况,请按中断键。

12.4 expr

计算表达式

12.5 exec 命令是shell内置的,他会用其后的程序的进程来取代当前的shell进程。

此功能的目的是节省系统资源,没有返回值。
当在shell脚本中运行exec时,该脚本及运行该脚本的shell会失踪,而被exec后的命令取代。
可以运行 exec cat 查看效果。当按下 ctrl + C 或 ctrl + D 时,shell窗口就会消失。

12.6 子shell

新开一个进程来运行子shell,新shell复制原shell的环境变量,而在其退出时,在这个新shell下对环境变量的任何更改不会影响到原shell。
将命令置于括号中,即可运行子shell。
#!/bin/sh
echo 'o:$PATH'
echo "Old:$PATH"
(PATH=/home/toma/Desktop:$PATH; echo "New:$PATH")
echo "Old:$PATH"

更快的复制
$ tar cf - orig | (cd target; tar xvf -)
用tar将orig整个目录树打包,并在target目录中解包。高效的复制了orig中的文件和目录。
这种做法保留了权限,且一般会比(cp -r)快。

12.7 在脚本中包含其他文件(.)

. config.sh
包换语法不会开启子shell,且便于在脚本中调用配置文件。

12.8 读取用户输入read

$ read var
将输入至于$var.

13. shell例子

a001 #!/bin/sh
echo First argument: $1
echo First argument: $2
echo First argument: $3
shift
echo First argument: $1
echo First argument: $2
echo First argument: $3
shift
echo First argument: $1
echo First argument: $2
echo First argument: $3
a003 #!/bin/sh
if [ $1 = hi ]; then
echo 'The first argument was "hi"'
else
echo -n 'The first argument was not "hi" -- '
echo It was '"'$1'"'
fi
  if [ "$1" = hi ]; then
if [ x"$1" = x"hi" ]; then
a005 #!/bin/sh
if grep -q daemon /etc/passwd; then
echo The daemon user is in the passwd file.
else
echo There is a big problem. daemon is not in the passwd file.
fi
  #!/bin/sh
if grep -q $1 /etc/passwd; then
echo The '"'$1'"' user is in the passwd file.
else
echo There is a big problem. '"'$1'"' is not in the passwd file.
fi
a007 #!/bin/sh
if [ "$1" = "hi" ]; then
echo 'The first argument was "hi"'
elif [ "$2" = "bye" ]; then
echo 'The second argument was "bye"'
else
echo -n 'The first argument was not "hi" and the second was not "bye"-- '
echo They were '"'$1'"' and '"'$2'"'
fi
a009 #!/bin/sh
for filename in *; do
if [ -f $filename ]; then
ls -l $filename
file $filename
else
echo $filename is not a regular file.
fi
done
a011 #!/bin/sh
case $1 in
bye)
echo Fine, bye.
;;
hi|hello)
echo Nice to see you.
;;
what*)
echo Whatever.
;;
*)
echo 'Huh?'
;;
esac
a013 #!/bin/sh
for str in one two three; do
echo $str
done
a015 #!/bin/sh
FILE=/tmp/whiletest.$$;
echo firstline> $FILE
while tail -10 $FILE | grep -q firstline;
do
# add lines to $FILE until tail -10 $FILE no longer prints "firstline"
echo -n Number of lines in $FILE:' '
wc -l $FILE | awk '{print $1}'
echo newline ?? $FILE
done

rm -f $FILE
a017 #!/bin/sh
FLAGS=$(grep ^flags /proc/cpuinfo | sed 's/.*://' | head -1)
echo Your processor supports:
for f in $FLAGS; do
case $f in
fpu) MSG="floating point unit"
;;
3dnow) MSG="3DNOW graphics extensions"
;;
mtrr) MSG="memory type range register"
;;
*) MSG="unknown"
;;
esac
echo $f: $MSG
Done
a019 #!/bin/sh
TMPFILE1=$(mktemp /tmp/im1.XXXXXX)
TMPFILE2=$(mktemp /tmp/im2.XXXXXX)
# trap "rm -f $TMPFILE1 $TMPFILE2; exit 1" INT
# --snip--
cat /proc/interrupts > $TMPFILE1
sleep 2
cat /proc/interrupts > $TMPFILE2
diff $TMPFILE1 $TMPFILE2
rm -f $TMPFILE1 $TMPFILE2
a021 #!/bin/sh
DATE=$(date)
UNIXDATE=$(date +%s)

cat <<EOF
Date: $DATE
UNIXDATE: $UNIXDATE
The output above is from the Unix date command.
It's not a very interesting command.
EOF
a023 #!/bin/sh
for file in *.fig; do
# exit if there are no files
if [ ! -f $file ]; then
exit
Fi
b=$(basename $file .gif)
echo Converting $b.gif to $b.png...
giftopnm $b.gif | pnmtopng > $b.png
done

 

14. 部分工具帮助

14.1 xargs

-0, --null items are separated by a null, not whitespace; disables quote and backslash processing and logical EOF processing items由null而不是空格分隔;禁用报价和反斜杠处理以及逻辑EOF处理
-a, --arg-file=FILE read arguments from FILE, not standard input 从FILE读取参数,而不是标准输入
-d, --delimiter=CHARACTER items in input stream are separated by CHARACTER, not by whitespace; disables quote and backslash processing and logical EOF processing 输入流中的项目由CHARACTER分隔,而不是由空格分隔;禁用报价和反斜杠处理以及逻辑EOF处理
-E END set logical EOF string; if END occurs as a line of input, the rest of the input is ignored (ignored if -0 or -d was specified) 设置逻辑EOF字符串;如果END作为输入行出现,则忽略输入的其余部分(如果指定了-0或-d则忽略)
-e, --eof[=END] equivalent to -E END if END is specified; otherwise, there is no end-of-file string 如果指定了END,则相当于-E END;否则,没有文件结束字符串
-I R same as --replace=R 与--replace = R相同
-i, --replace[=R] replace R in INITIAL-ARGS with names read from standard input; if R is unspecified, assume {} 用INITIAL-ARGS中的R替换从标准输入读取的名称;如果R未指定,则假设{}
-L, --max-lines=MAX-LINES use at most MAX-LINES non-blank input lines per command line 每个命令行最多使用MAX-LINES非空白输入行
-l[MAX-LINES] similar to -L but defaults to at most one nonblank input line if MAX-LINES is not specified 类似于-L,但如果未指定MAX-LINES,则默认为最多一个非空白输入行
-n, --max-args=MAX-ARGS use at most MAX-ARGS arguments per command line 每个命令行最多使用MAX-ARGS参数
-P, --max-procs=MAX-PROCS run at most MAX-PROCS processes at a time 一次运行大多数MAX-PROCS进程
-p, --interactive prompt before running commands 运行命令前提示
--process-slot-var=VAR set environment variable VAR in child processes 在子进程中设置环境变量VAR
-r, --no-run-if-empty if there are no arguments, then do not run COMMAND; if this option is not given, COMMAND will be run at least once 如果没有参数,则不要运行COMMAND;如果未给出此选项,COMMAND将至少运行一次
-s, --max-chars=MAX-CHARS limit length of command line to MAX-CHARS 将命令行的长度限制为MAX-CHARS
--show-limits show limits on command-line length 显示命令行长度的限制
-t, --verbose print commands before executing them 在执行它们之前打印命令
-x, --exit exit if the size (see -s) is exceeded 如果超出大小(请参阅-s),则退出
--help display this help and exit 显示此帮助并退出
--version output version information and exit 输出版本信息并退出

14.2 expr

ARG1 < ARG2 ARG1 is less than ARG2 ARG1小于ARG2
ARG1 <= ARG2 ARG1 is less than or equal to ARG2 ARG1小于或等于ARG2
ARG1 = ARG2 ARG1 is equal to ARG2 ARG1等于ARG2
ARG1 != ARG2 ARG1 is unequal to ARG2 ARG1与ARG2不相等
ARG1 >= ARG2 ARG1 is greater than or equal to ARG2 ARG1大于或等于ARG2
ARG1 > ARG2 ARG1 is greater than ARG2 ARG1大于ARG2
ARG1 + ARG2 arithmetic sum of ARG1 and ARG2 ARG1和ARG2的算术和
ARG1 - ARG2 arithmetic difference of ARG1 and ARG2 ARG1和ARG2的算术差异
ARG1 * ARG2 arithmetic product of ARG1 and ARG2 ARG1和ARG2的算术乘积
ARG1 / ARG2 arithmetic quotient of ARG1 divided by ARG2 ARG1的算术商除以ARG2
ARG1 % ARG2 arithmetic remainder of ARG1 divided by ARG2 ARG1的算术余数除以ARG2
     
ARG1 | ARG2 ARG1 if it is neither null nor 0, otherwise ARG2 ARG1如果它既不是null也不是0,否则是ARG2
ARG1 & ARG2 ARG1 if neither argument is null or 0, otherwise 0 ARG1如果两个参数都不为null或0,否则为0
STRING : REGEXP anchored pattern match of REGEXP in STRING STRING中REGEXP的锚定模式匹配
match STRING REGEXP same as STRING : REGEXP 与 STRING : REGEXP 相同
substr STRING POS LENGTH substring of STRING, POS counted from 1 STRING的子串,POS从1开始计算
index STRING CHARS index in STRING where any CHARS is found, or 0 STRING中找到任何CHARS的索引,或0
length STRING length of STRING STRING的长度
+ TOKEN interpret TOKEN as a string, even if it is a keyword like 'match' or an operator like '/' 将TOKEN解释为字符串,即使它是像'匹配'这样的关键字或像'/'这样的运算符
( EXPRESSION ) value of EXPRESSION 表达式的值


15. 更多学习相关链接

https://edu.aliyun.com/course/155/lesson/list?utm_content=m_42087
Shell 编程入门到精通 13课时 | 6207人已学 | 阿里云免费课程

Shell 编程范例
项目首页:http://www.tinylab.org/open-shell-book
代码仓库:https://github.com/tinyclub/open-shell-book
在线阅读:http://tinylab.gitbooks.io/shellbook

Linux Shell脚本教程:30分钟玩转Shell脚本编程
http://c.biancheng.net/cpp/shell/