0. shell的作用
Shell就是命令行工具的胶水,没有任何语言能像Shell一样方便地将一大堆命令行工具组合起来。原则上来说,Shell做什么都可以,但显然它最适合的是自动化,因为只需要将你原来手动敲的命令都复制到一个文件里面就行了。
Shell跟标准的编程语言区别很大,它基本上是一个面向字符串的编程语言,组合用好awk/sed/grep,偶尔配合eval,有时候会发挥奇效。
1. shell基本语法
1.1 变量
1.1.1 变量概念及使用
按照惯例,Shell变量通常由字母加下划线开头,由任意长度的字母、数字、下划线组成。有两种类型的Shell变量:
1.环境变量
环境变量可以从父进程传给子进程,因此Shell进程的环境变量可以从当前Shell进程传给fork出来的子进程。用printenv命令可以显示当前Shell进程的环境变量。
2.本地变量
只存在于当前Shell进程。
3. 变量赋值
环境变量是任何进程都有的概念,而本地变量是Shell特有的概念。在Shell中,环境变量和本地变量的定义和用法相似。在Shell中定义或赋值一个变量:
VAR_NAME=value
注意等号两边都不能有空格,否则会被Shell解释成命令和命令行参数。
一个变量定义后仅存在于当前Shell进程,它是本地变量,用export命令可以把本地变量导出为环境变量
4. 用unset删除变量
unset VAR_NAME
5. 引用变量
$VAR_NAME
1.1.2 特殊变量
$#:查看变量参数的个数 $0:查看脚本的名字 $!:查看shell后台的pid $@:查看传递脚本所有参数的列表 $*:查看所有参数的列表,单字符串形式显示 $$:脚本本身进程的ID $?:上一条命令的结果,显示0则成功,不是0则失败
1.1.3 内部环境变量
$PATH SHELL 当前使用的shell UID 当前的用户环境 {0|其它数字}={root|其它用户} HOME 当前使用的用户目录 PWD 当前的目录 HISTFILE 历史命令路径 PS1 #[\u@\h \W]\$ 用户@主机名\目录\$
1.2 单引号和双引号
被双引号用括住的内容,将被视为单一字串。它防止通配符扩展,但允许变量扩展。这点与单引号的处理方式不同
1.3 条件测试
shell中提供两种条件判断的方法。
1. test指令
overboom@overboom:~/Work $ var=2 overboom@overboom:~/Work $ test $var -gt 1 overboom@overboom:~/Work $ echo $? 0 overboom@overboom:~/Work $ overboom@overboom:~/Work $ overboom@overboom:~/Work $ test $var -eq 3 overboom@overboom:~/Work $ echo $? 1 overboom@overboom:~/Work $ test $var -eq 2 overboom@overboom:~/Work $ echo $? 0
$? 代表上一个命令执行后的退出状态
2. [ condition ] 注意condition前后要有空格。非空返回0,0为 true,否则为 false 。
overboom@overboom:~/Work$ var=2 overboom@overboom:~/Work$ [ $var -gt 3 ] overboom@overboom:~/Work$ echo $? 1 overboom@overboom:~/Work$ [ $var -eq 2 ] overboom@overboom:~/Work$ echo $? 0 overboom@overboom:~/Work$
常见测试指令如下:
[ -d DIR ] 如果DIR存在并且是一个目录则为真
[ -f FILE ] 如果FILE存在且是一个普通文件则为真
[ -z STRING ] 如果STRING的长度为零则为真
[ -n STRING ] 如果STRING的长度非零则为真
[ STRING1 = STRING2 ] 如果两个字符串相同则为真
[ STRING1 != STRING2 ] 如果字符串不相同则为真
[ ARG1 OP ARG2 ] ARG1和ARG2应该是整数或者取值为整数的变量,OP是-eq(等于)-ne(不等于)-lt(小于)-le(小于等于)-gt(大于)-ge(大于等于)之中的一个
3. 条件测试与逻辑运算符
和C语言类似,测试条件之间还可以做与、或、非逻辑运算:
[ ! EXPR ] EXPR可以是上表中的任意一种测试条件,!表示“逻辑反(非)”
[ EXPR1 -a EXPR2 ] EXPR1和EXPR2可以是上表中的任意一种测试条件,-a表示“逻辑与”
[ EXPR1 -o EXPR2 ] EXPR1和EXPR2可以是上表中的任意一种测试条件,-o表示“逻辑或”
1.4 分支结构
1.4.1 if/then/elif/else/fi
if语法如下:
多分支: if [ 条件 ];then statement1 ..... elif [ 条件2 ];then statement2 .... else statement3 .... fi
和C语言类似,在Shell中用if、then、elif、else、fi这几条命令实现分支控制。
下面看一段示例code
#! /bin/sh if [ -f /bin/bash ] then echo "/bin/bash is a file" else echo "/bin/bash is NOT a file" fi if :; then echo "always true"; fi
与C类似的语法,并不难理解。
“:”是一个特殊的命令,称为空命令,该命令不做任何事,但Exit Status总是真。此外,也可以执行/bin/true或/bin/false得到真或假的Exit Status。
1.4.2 case/esac
case语法如下:
case $变量名 in 'value1') statement ... ;; 'value2') statement ... ;; *) statement .. ;; esac #case支持的通配符: * //任意长度任意字符 ? //任意单个字符 [] //指字范围内的任意单个字符 start|START //俩种选择
case命令可类比C语言的switch/case语句,esac表示case语句块的结束。C语言的case只能匹配整型或字符型常量表达式,而Shell脚本的case可以匹配字符串和Wildcard,每个匹配分支可以有若干条命令,末尾必须以;;结束,执行时找到第一个匹配的分支并执行相应的命令,然后直接跳到esac之后,不需要像C语言一样用break跳出。
#! /bin/sh echo "Is it morning? Please answer yes or no." read YES_OR_NO case "$YES_OR_NO" in yes|y|Yes|YES) echo "Good Morning!";; [nN]*) echo "Good Afternoon!";; *) echo "Sorry, $YES_OR_NO not recognized. Enter yes or no." return 1;; esac return 0
1.5 循环
1.5.1 for/do/done
for语法如下:
第一种: for ((expr1;expr2;expr3)) # expr1:初始值条件 #expr2:循环的范围进行退出 #expr3:变量的值使用 { 循环体 } for ((expr1;expr2;expr3));do 循环体 done 第二种: for 变量 in 列表; do 循环体 done
Shell脚本的for循环结构和C语言很不一样,它类似于某些编程语言的foreach循环。例如:
#! /bin/sh for FRUIT in apple banana pear; do echo "I like $FRUIT" done
1.5.2 while/do/done
while语法如下:
while循环用于不知道循环次数的场景,注意有退出条件 while [ 条件 ];do statement ..... done
while的用法和C语言类似。比如一个验证密码的脚本:
#! /bin/sh echo "Enter password:" read TRY while [ "$TRY" != "secret" ]; do echo "Sorry, try again" read TRY done
1.5.3 break和continue
break[n]可以指定跳出几层循环;continue跳过本次循环,但不会跳出循环。
即break跳出,continue跳过。
2 位置参数和特殊变量
shell中常用的位置参数和特殊变量总结:
$0 相当于C语言main函数的argv[0] $1、$2... 这些称为位置参数(Positional Parameter),相当于C语言main函数的argv[1]、argv[2]... $# 相当于C语言main函数的argc - 1,注意这里的#后面不表示注释 $@ 表示参数列表"$1" "$2" ...,例如可以用在for循环中的in后面。 $* 表示参数列表"$1" "$2" ...,同上 $? 上一条命令的Exit Status $$ 当前进程号
#!/bin/bash echo ------start-------- echo $0 $1 $2 echo $* echo $@ echo $$ echo $# echo ------end--------
输出如下:
可以在shell脚本中使用shift丢弃一些参数
#!/bin/bash echo ------start-------- echo ------before shift-------- echo $0 $1 $2 $3 $4 $5 echo $# shift shift shift echo ------after shift-------- echo $0 $1 $2 $3 $4 $5 echo $# echo ------end--------
输出如下:
3. 输入和输出
3.1 echo
显示文本行或变量,或者把字符串输入到文件。
echo [option] string -e 解析转义字符 -n 不回车换行。默认情况echo回显的内容后面跟一个回车换行。
3.2 tee
tee命令把结果输出到标准输出,另一个副本输出到相应文件。
overboom@overboom:~/Work$ ls -al total 52 drwxrwxr-x 11 overboom overboom 4096 9月 12 22:47 . drwxr-xr-x 22 overboom overboom 4096 9月 12 22:47 .. drwxr-xr-x 7 root root 4096 7月 4 18:39 beanCode drwxrwxr-x 2 overboom overboom 4096 3月 27 23:45 c_code drwxrwxr-x 3 overboom overboom 4096 8月 10 21:07 fdbus drwxrwxr-x 3 overboom overboom 4096 8月 10 23:16 git_usage drwxrwxr-x 4 overboom overboom 4096 6月 2 00:08 jsoncpp drwxrwxr-x 2 overboom overboom 4096 8月 4 21:20 minixml -rwxrwxr-x 1 overboom overboom 213 9月 12 22:45 position.sh drwxrwxr-x 2 overboom overboom 4096 5月 27 23:43 test_C++Sqlite drwxrwxr-x 6 overboom overboom 4096 5月 25 21:37 test_find -rwxrwxr-x 1 overboom overboom 141 9月 12 21:23 test.sh drwxrwxr-x 3 overboom overboom 4096 4月 10 17:36 xwp_linux overboom@overboom:~/Work$ ls -al | tee out_file total 52 drwxrwxr-x 11 overboom overboom 4096 9月 12 23:02 . drwxr-xr-x 22 overboom overboom 4096 9月 12 22:47 .. drwxr-xr-x 7 root root 4096 7月 4 18:39 beanCode drwxrwxr-x 2 overboom overboom 4096 3月 27 23:45 c_code drwxrwxr-x 3 overboom overboom 4096 8月 10 21:07 fdbus drwxrwxr-x 3 overboom overboom 4096 8月 10 23:16 git_usage drwxrwxr-x 4 overboom overboom 4096 6月 2 00:08 jsoncpp drwxrwxr-x 2 overboom overboom 4096 8月 4 21:20 minixml -rwxrwxr-x 1 overboom overboom 213 9月 12 22:45 position.sh drwxrwxr-x 2 overboom overboom 4096 5月 27 23:43 test_C++Sqlite drwxrwxr-x 6 overboom overboom 4096 5月 25 21:37 test_find -rwxrwxr-x 1 overboom overboom 141 9月 12 21:23 test.sh drwxrwxr-x 3 overboom overboom 4096 4月 10 17:36 xwp_linux overboom@overboom:~/Work$ ll total 56 drwxrwxr-x 11 overboom overboom 4096 9月 12 23:02 ./ drwxr-xr-x 22 overboom overboom 4096 9月 12 22:47 ../ drwxr-xr-x 7 root root 4096 7月 4 18:39 beanCode/ drwxrwxr-x 2 overboom overboom 4096 3月 27 23:45 c_code/ drwxrwxr-x 3 overboom overboom 4096 8月 10 21:07 fdbus/ drwxrwxr-x 3 overboom overboom 4096 8月 10 23:16 git_usage/ drwxrwxr-x 4 overboom overboom 4096 6月 2 00:08 jsoncpp/ drwxrwxr-x 2 overboom overboom 4096 8月 4 21:20 minixml/ -rw-rw-r-- 1 overboom overboom 793 9月 12 23:02 out_file -rwxrwxr-x 1 overboom overboom 213 9月 12 22:45 position.sh* drwxrwxr-x 2 overboom overboom 4096 5月 27 23:43 test_C++Sqlite/ drwxrwxr-x 6 overboom overboom 4096 5月 25 21:37 test_find/ -rwxrwxr-x 1 overboom overboom 141 9月 12 21:23 test.sh* drwxrwxr-x 3 overboom overboom 4096 4月 10 17:36 xwp_linux/ overboom@overboom:~/Work$ cat out_file total 52 drwxrwxr-x 11 overboom overboom 4096 9月 12 23:02 . drwxr-xr-x 22 overboom overboom 4096 9月 12 22:47 .. drwxr-xr-x 7 root root 4096 7月 4 18:39 beanCode drwxrwxr-x 2 overboom overboom 4096 3月 27 23:45 c_code drwxrwxr-x 3 overboom overboom 4096 8月 10 21:07 fdbus drwxrwxr-x 3 overboom overboom 4096 8月 10 23:16 git_usage drwxrwxr-x 4 overboom overboom 4096 6月 2 00:08 jsoncpp drwxrwxr-x 2 overboom overboom 4096 8月 4 21:20 minixml -rwxrwxr-x 1 overboom overboom 213 9月 12 22:45 position.sh drwxrwxr-x 2 overboom overboom 4096 5月 27 23:43 test_C++Sqlite drwxrwxr-x 6 overboom overboom 4096 5月 25 21:37 test_find -rwxrwxr-x 1 overboom overboom 141 9月 12 21:23 test.sh drwxrwxr-x 3 overboom overboom 4096 4月 10 17:36 xwp_linux
3.3 文件重定向
cmd > file 把标准输出重定向到新文件中 cmd >> file 追加 cmd > file 2>&1 标准出错也重定向到1所指向的file里 cmd >> file 2>&1 cmd < file1 > file2 输入输出都定向到文件里 cmd < &fd 把文件描述符fd作为标准输入 cmd > &fd 把文件描述符fd作为标准输出 cmd < &- 关闭标准输入
4.函数
和C语言类似,Shell中也有函数的概念,但是函数定义中没有返回值也没有参数列表。
Shell函数没有参数列表并不表示不能传参数,事实上,函数就像是迷你脚本,调用函数时可以传任意个参数,在函数内同样是用$0、$1、$2等变量来提取参数,函数中的位置参数相当于函数的局部变量,改变这些变量并不会影响函数外面的$0、$1、$2等变量。函数中可以用return命令返回,如果return后面跟一个数字则表示函数的Exit Status。
5. shell脚本调试方法
Shell提供了一些用于调试脚本的选项,如:
-n 读一遍脚本中的命令但不执行,用于检查脚本中的语法错误。
-v 一边执行脚本,一边将执行过的脚本命令打印到标准错误输出。
-x 提供跟踪执行信息,将执行的每一条命令和结果依次打印出来。
这些选项有三种常见的使用方法:
1.在命令行提供参数。如: $ sh -x ./script.sh 2.在脚本开头提供参数。如: #! /bin/sh -x 3.在脚本中用set命令启用或禁用参数。如: #! /bin/sh if [ -z "$1" ]; then set -x echo "ERROR: Insufficient Args." exit 1 set +x fi
set -x和set +x分别表示启用和禁用-x参数,这样可以只对脚本中的某一段进行跟踪调试。