写之前我们先来搞清楚为什么要学shell,学习要有目的性
shell简单、灵活、高效,特别适合处理一些系统管理方面的小问题
shell可以实现自动化管理,让系统管理员的工作变得容易、简单、高效
shell脚本可移植性好,在unix/linux系统中可灵活移植,几乎不用任何设置就能正常运行
shell脚本可轻松方便读取和修改源代码,不需要编译
掌握shell可以帮你解决一些故障问题,比如脚本引起的故障问题
掌握shell是一个中级以上系统工程师必需要会的
掌握shell是你系统管理进阶的必经之路
掌握shell是你面试更高级职位的一块敲门砖
那什么时候不使用Shell 脚本?
资源密集型的任务,尤其在需要考虑效率时(比如排序,hash 等)
需要处理大任务的数学操作,尤其是浮点运算,精确运算,或者复杂的算术运算(这种情况一般使用C++或FORTRAN 来处理)
有跨平台移植需求(一般使用C 或Java)
复杂的应用,在必须使用结构化编程的时候(需要变量的类型检查,函数原型,等等)
对于影响系统全局性的关键任务应用。
对于安全有很高要求的任务,比如你需要一个健壮的系统来防止***,破解,恶意破坏等等.
项目由连串的依赖的各个部分组成。
需要大规模的文件操作
需要多维数组的支持
需要数据结构的支持,比如链表或数等数据结构
需要产生或操作图形化界面 GUI
需要直接操作系统硬件
需要 I/O 或socket 接口
需要使用库或者遗留下来的老代码的接口
私人的,闭源的应用(shell 脚本把代码就放在文本文件中,全世界都能看到)
 如果你的应用符合上边的任意一条,那么就考虑一下更强大的语言吧--或许是Perl,Python,
Ruby, 或者是更高层次的编译语言比如C/C++,Java.

本文写的都是bash相关,请对号入座
1.shell脚本是什么?组成有哪些?
通俗地讲,shell脚本就是写有一堆系统命令+简单的shell语法(变量、if判断、循环语句等)的一个文件,执行这文件能把所有命令一次性都执行了并实现一定的目的。所以要学好shell,必须要把系统一些常用的系统管理命令及文本操作命令(grep、sed、awk、sort、cut、tr、uniq、join等)掌握了,要能做到信手拈来,想实现什么功能就知道用什么命令才行,然后再学习下shell语法就可以了,shell语法比其它语言简单多了,你只需学习半日便可基本掌握。
2.怎么执行一个脚本?比如执行一个刚写好的脚本aa.sh
给用户一个读与执行的权限(chmod u+rx  aa.sh),就可以用./aa.sh来执行脚本(这样执行是开启一个子shell来执行的)如果想在当前shell中执行脚本,只用给读的权限就行了,用bash aa.sh或者.  aa.sh或者source aa.sh皆可执行, 不过这样执行,会将bash的一些特定扩展功能关闭,脚本可能因此而调用失败,所以不建议这样执行脚本。生产中都是给脚本一个执行权限直接开启一子shell来执行的。
3.脚本第一行怎么都是以#!开始的?代表什么意思?
#!(读音:sha-bang)实际是一个2字节的魔法数字,这是指定一个文件类型的特殊标记,它就代表一个可执行的脚本,后面跟一个路径名(注意:如果是有unix味道的脚本在#!后跟一空格再跟路径名),这个路径名指定了一个解释脚本中命令的程序,这个程序可以是shell、程序语言或者是任意一个通用程序如:
#!/bin/sh
#!/bin/bash
#!/usr/bin/perl
#!/usr/bin/tcl
#!/usr/awk -f
#!/bin/sed -f
弄个好玩的哈,随便找一个脚本打开将第一行改为#!/bin/rm,执行下这个脚本,看看有什么效果
???脚本怎么没了?对,这样改的效果就是脚本将自己删除什么也不做。
4.shell内部变量
$SHELL  显示当前系统用的shell
$BASH   显示bash路径
$BASH_SUBSHELL  提示当前subshell的层次
$BASH_VERSION  显示bash版本
$BASH_VERSINFO[n]  显示bash安装信息的一个6元素数组,与$BASH_VERSION 很像
$DIRSTACK 、$PWD  结果 等于dirs命令结果
$EDITOR  脚本调用的默认编辑器
$EUID   “effective”用户ID号
$FUNCNAME  当前函数名字
$GROUPS  当前用户属于的组
$UID 用户ID号
$HOME  用户home目录
$HOSTNAME   系统主机名
$IFS  内部域分隔符,默认为空白(空格、tab、新行)
$LINENO  记录它所在脚本中它所在行和行号,一般用于调度
$MACHTYPE  显示系统类型,系统架构
$OLDPWD  老的工作目录
$OPTYPE  操作系统类型
$PATH  指向Bash外部命令所在位置,系统在它指向的目录下搜索命令
$PPID  父进程的进程ID
$PROMT_COMMAND  保存一个在主提示符显示之前需要执行的命令
$PS1  主提示符
$PS2  第二提示符,当需要额外输入时显示,默认为">"
$PS3 第三提示符,在一个select循环中显示
$PS4 第四提示符,当使用-x选项调用脚本时,这个提示符将出现在每行的输出前边,默认为"+"
$REPLY  read命令如果没有给变量,那么输入将保存在$REPLY 中.在select 菜单中也可用,但是只
提供选择的变量的项数,而不是变量本身的值.
$SECONDS  这个脚本已经运行的时间(单位为秒).
$SHELLOPTS  保存shell允许的选项
$SHLVL   shell层次
$TMOUT 如果$TMOUT 环境变量被设置为一个非零的时间值,那么在过了这个指定的时间之后,
shell提示符将会超时,这会引起一个logout.
5.预定义变量,适用所有shell,无法更改
$#   取出位置参数个数
$*   取出所有位置参数内容
$?  判断上一次命令执行结果是否正确,0代表正确,非0则不代表不正确
$$  当前进程进程号
$!  后台运行的最后一进程号
$0  取出脚本名子
$1、$2、$3……位置参数
脚本位置参数个数如果超过9要用{}括起来,如${10}
$_  保存之前执行的命令的最后一个参数
6.自定义变量
bash中变量无类型区分
aa=abc123    定义变量并赋值abc123
aa=          定义空变量或者清空变量aa,但变量还存在
export test="hello world"  设定环境变量test
export或者export -p        显示所有环境变量

declare / typeset 选项 变量名
declare 或 typeset 有同样的功能:指定变量属性。如果使用 declare 后面并没有接任何参数,那么 bash 就会主动的将所有的变量名称与内容通通叫出来,就好像使用 set 一样!
选项:
-a 将后面的变量定义成为数组 (array)
-i 将后面的变量定义成为整数(integer)
-x 将后面的变量变成环境变量,同export 一样,
-r 将后面的变量设定为只读 ,该变量不可被更改内容,也不能 unset
-f 列出脚本中的函数

readonly用来设置只读变量
readonly 变量名
readonly -f 函数名称
readonly -a 数组变量

变量间接引用 eval var1=\$$var2
7.shell脚本中的一些特殊字符
#     后面的内容到行尾都是注释,不会执行(第一行的#!是个例外)
     注意:echo命令中被转义的#不能作为注释,在特定的参数替换结构或数字常量表达式中也不是注释
   如 echo ${PATH#*:}
         echo $((2#101011))   
\       转义字符
;   命令分隔符,可以用来在一行中写多个命令
;;  终止case结构中选项
,   逗号链接了一系列的算术操作,虽然里面的内容都被运行了,最后一项被返回
`  后置引用,命令替换
:  空命令等价于NOP,也可认为与true作用相同
  可以充当占位符,例如
     if [ ]
     then :      #什么都不做,引出分支
     else
             .................
      fi
8.linux终端下的一些常用快捷键,可以加快操作速度的
Ctrl+a   移到命令行首
Ctrl+e    移到命令行尾
Ctrl+u   删除到行首的命令
Ctrl+k   删除到行尾的命令
Ctrl+a后再Ctrl+k  或者Ctrl+e后再Ctrl+u就是删除输入的全部命令
Ctrl+->/<-  向左/右移动一个单词(远程ssh终端不可用)
Ctrl+c  终止当前任务
Ctrl+d  登出shell
Ctrl+b  光标后退
Ctrl+h  删除光标前的字符
Ctrl+w 删除光标前的一个单词
Ctrl+j  新起一行进行输入
Ctrl+l  相当于clear,清屏
Ctrl+z  终止前台工作
Ctrl+v  插入控制字符
Ctrl+s 挂起命令执行
Ctrl+q 继续命令执行
Esc+.   重新调用前一个命令中的参数,非常有用!
9.if判断都有哪些格式?
格式一:
if  [  ];then
........
fi
等价于
if  [  ]
then
...........
fi
格式二:
if  [ ]
then
..........
else
..........
if
格式三:
if [ ]
then
......
elif [ ]
then
.......
fi
格式四:
if [ ]
then
......
elif [ ]
then
.......
elif [ ]
then
.......
elif [ ]
........
elif
.......
fi
if-grep结构:
if grep -q aa  book.txt
then echo "book.txt至少有一个字符串aa"
fi
10.if判断有哪些参数?
 -b 当文件存在并且是块文件时返回真
 -c 当文件存在并且是字符文件时返回真
 -d 当目录存在时返回真
 -e 当文件或目录存在时返回真
 -f 当文件存在并且是正规文件(不是目录或者设备文件)时返回真
 -g 当文件或目录存在并且设置了SGID位时返回为真
 -h 当文件存在并且是符号链接文件时返回真,该选项在一些老系统上无效
 -k 当文件或目录存在并且设置了“粘滞”位时返回真
 -L  当文件是个符号链接返回真
 -N 当从文件最后被阅读到现在被修改过时返回真
 -O 当文件或目录存在并且被子当前进程的有效用户ID所指定的用户拥有时返回真。
 -p 当文件存在并且是命令管道时返回为真
 -r 当文件或目录存在并且可读时返回为真
 -s 当文件大小大于0时返回真
 -S 当文件是个socket时返回真
 -t 关联到一个终端设备的文件描述符这个选项一般都用来检测是否在一个给定脚本中的 stdin[-t0]或[-t1]是一个终   端
 -u 当文件或目录存在并且设置了SUID位时返回真
 -w 当文件或目录存在并且可写时返回真。
 -x 当文件或目录存在并且可执行时返回真。一个目录为了它的内容被访问必然是可执行的。
 -z  变量是空串时返回真
 -n 变量是非空串时返回真 
比较字符写法:
 -eq 等于
 -ne 不等于
 -gt 大于
 -lt 小于
 -le 小于等于
 -ge 大于等于
 = 两个字符相等
 != 两个字符不等
 11.case结构:
case 变量 in
  "变量值1")
                 ..................
                          ;;
  "变量值2")
                    ...............
                           ;;
      *)
                       ................
                             ;;
esac
12.循环结构:
(1)while循环
while [ ]
do
..........
done
或者while [ ];do
      ........
      done
例:一个简单的死循环
while true
do
   echo "hello"
done
(2)for循环
for 变量  in  取值列表    //取值列表可以是如1 2 3 4 5或者{1..5}或者`seq 5`或者`命令`形式
do
..........
done
(3)until循环
until [ ]
do
........
done
13.()中写命令与{}中写命令有什么区别?
()中命令执行时会开启一个新的shell,{}中的命令执行时不能正常开启一个新的shell,管道中的{}例外
14.[ ]与` `条件测试有什么区别?
[ ]是shell内建的测试命令,` `是由外置命令/usr/bin/test进行测试,[[]]结构比Bash 的[]更加灵活,在[[]]结构中,将没有文件扩展或者是单词分离,但是会发生参数扩展和命令替换.:使用[[]],而不是[],能够阻止脚本中的许多逻辑错误.比如,尽管在[]中将给出一个错误,但是&&,||,<>操作还是能够工作在一个[[]]test 之中.
15.实现标准错误伴随标准输出做转向
命令 &>/dev/null
命令 >/dev/null 2>&1
命令 >&/dev/null
16.双引号" "中的引用叫部分引用,里面内容会发生变量替换,单引号' '中的引用叫全引用,不会发生变量替换
双引号阻止了所有里面的特殊字符的重新解释,但是$ ` \除外,使用双引号可以防止单词分隔
17.如何将一命令执行结果赋值给一变量?
aa=`命令`
aa=$(命令)
18.怎么清空一文件aa.txt?
>aa.txt
:>aa.txt       #只适用于正规文件,不适用于管道,符号链接和某些特殊文件
cat /dev/null >aa.txt 
19.echo $'\042'   打印8进制字符
echo $'\x22'   打印16进制字符
echo -e 输出转义字符
echo -e 输出转义字符
echo -e  "\n"  换行
          \c  最后不加上换行符
          \r  回行首
          \t  水平TAB
          \v  垂直TAB
          \' 单引号
          \" 双引号
          \\ 斜线\
          \0  后接八进制数字,显示对应的ASCII字符
echo -e  "\033[40;35mHello,world\033[0m"   显示黑底紫色字体Hello,world
背景颜色范围:40 41 42 43 44 45  46   47 48 49
字体颜色范围:30 31 32 33 34 35  36   37 38 39
              黑 红 绿 黄 蓝 紫 深绿  白
20.字符串操作
${变量-默认值}      如果变量不存在传回默认值
${变量:-默认值}     如果变量不存在或值为空传回默认值
${变量=默认值}      如果变量不存在传回默认值
${变量:=默认值}     如果变量不存在或值为空传回默认值
${变量+修改值}      如果变量存在传回修改值
${变量:+修改值}     如果变量存在且值不为空传回修改值
${变量?提示信息}   如果变量不存则显示提示信息并停止执行脚本
${变量:?提示信息}  如果变量不存在或值为空则显示提示信息并停止执行脚本

注:字符串中位置编号01234……,下面的样式指正则表达式
${#变量}                                 显示变量字符串长度
expr length "字符串"                     显示字符串长度
expr "字符串" : '样式'             显示正则表达式匹配的子串长度
expr match "字符串" '样式'         显示正则表达式匹配的子串长度
expr index "字符串" 子串                 显示子串中最小字符在字符串中的位置
expr substr "字符串"  起始位置 长度      显示起始位置开始的指定长度的子串

${变量:位置起点}                由指定的位置开始,截取到字符串结束
${变量:位置起点:长度}          由指定的位置开始,截取指定长度的子字符串
${@:起点}                       由起点开始,取得后面所有的位置参数  //@换成*号效果相同
${@:起点:个数}                  由起点开始,取得指定个数的位置参数
   
${变量#样式}   由最左边开始对比变量值,删除“最短符合的字符串”
${变量##样式}  由最左边开始对比变量值,删除“最长符合的字符串”
${变量%样式}   由最右边开始对比变量值,删除“最短符合的字符串”
${变量%%样式}  由最右边开始对比变量值,删除“最长符合的字符串”

${变量/样式/替换字符串}       替换第一个对比符合的字符串
${变量//样式/替换字符串}      替换全部对比符合的字符串
${变量/#样式/替换字符串}      替换第一个对比符合的字符串,样式要出现在变量值开头
${变量/样式/}                 删除第一个对比符合的字符串
${变量//样式/}                删除全部对比符合的字符串
${变量/#样式/}                删除第一个对比符合的字符串,样式要出现在变量值开头
${变量/%样式/}                删除第一个对比符合的字符串,样式要出现在变量值结尾

${!开头字符串@}或
${!开头字符串*}        把所有以指定字符串开头的变量名称列出,各变量间用$IFS定义的第一个分隔符(通常是空格)隔开

${!数组变量[@]}或
${!数组变量[*]}        把数组变量所有索引列出,各索引值间用$IFS定义的第一个分隔符(通常是空格)隔开
21.help  显示所有bash内置命令,用“help 命令”无结果的说明这个命令不是内置命令
22.set 显示shell所有内部变量和函数
set -o 查看shell所有属性的开关状态
set -o emacs 打开emacs模式
set +o emacs 关闭emacs模式(不能用上下键查找历史命令)
set -C 保护已存在的文件,避免转向输出时被覆盖掉文件的内容  同set -o noclobber
set -c 恢复正常
set -v 将脚本执行的每一进程程序代码显示出来,一般用于shell排错
set -x 将执行跟踪功能打开
set +x 关闭跟踪执行功能
unset -v 变量名称   取消变量
unset -f 函数名称   取消函数

23.shopt 显示shell行为模式各选项开关状态
shopt -s 启用选项
shopt -u 关闭选项
shopt -o 同set -o

shopt -s -o nounset 强制变量一定要经过声明才能使用

24.多行注释方法
:<<END
第一行批注
第二行批注
第三行批注
……
END
25.正则表达式
\b是正则表达式规定的一个元字符,代表着单词的开头或结尾,也就是单词的分界处。如果要精确地查找hi这个单词的话,我们应该使用\bhi\b。
.是另一个元字符,匹配除了换行符以外的任意字符。
*同样是元字符,不过它代表的不是字符,也不是位置,而是数量——它指定*前边的内容可以连续重复使用任意次以使整个表达式得到匹配。因此,.*连在一起就意味着任意数量的不包含换行的字符。
+ 匹配重复1次或更多次
? 匹配重复零次或一次
{n} 重复n次
{n,} 重复n次或更多次
{n,m} 重复n到m次 
\d匹配一位数字                 0\d\d-\d\d\d\d\d\d\d\d  等同于 0\d{2}-\d{8}
\s匹配任意的空白符,包括空格,制表符(Tab),换行符,中文全角空格等。
\w匹配字母或数字或下划线或汉字等。
^ 匹配字符串的开始
$ 匹配字符串的结束
[0-9]代表的含意与\d就是完全一致的
[a-z0-9A-Z_]也完全等同于\w

(\d{1,3}\.){3}\d{1,3}是一个简单的IP地址匹配表达式
POSIX 字符类. [:class:]
这是另外一个可选的用于指定匹配字符范围的方法.
[:alnum:] 匹配字母和数字.等同于A-Za-z0-9.
[:alpha:] 匹配字母. 等同于A-Za-z.
[:blank:] 匹配一个空格或是一个制表符(tab).
[:cntrl:] 匹配控制字符.
[:digit:] 匹配(十进制)数字. 等同于0-9.
[:graph:] (可打印的图形字符). 匹配 ASCII 码值的33 - 126 之间的字符. 这和下面提到的
[:print:]一样,但是不包括空格字符.
[:lower:] 匹配小写字母. 等同于a-z.
[:print:] (可打印字符). 匹配 ASCII 码值 32 - 126 之间的字符. 这和上面提到的一样
[:graph:],但是增多一个空格字符.
[:space:] 匹配空白字符 (空格符和水平制表符).
[:upper:] 匹配大写字母. 等同于A-Z.
[:xdigit:] 匹配十六进制数字. 等同于0-9A-Fa-f.
注意: POSIX 字符类一般都要求用引号或是双方括号` `引起来.
26.算术扩展
$((算术式))
expr 算术式
$[算术式]
declare -i  变量=算术式
let  算术式
27.read 由标准输入读取一行数据
用例1:
echo "请输入你的名字"
read yname
echo "你的名字是:$yname"     
用例2:
read -p '请输入你的英文名字:'
echo '你的名字是:' $REPLY
用例3:
read -p "请输入你的英文名字: " -t 30 name
echo "你的名字是: $name"
用例4:
read LINE <data  将data的第一行放入变量LINE中
用例5:
read f1 f2 f3 f4 <data 如果data中数据行以空格分隔,用此读取各字段值
用例6:
IFS=':'
read f1 f2 f3 f4 f5 f6 f7 </etc/passwd 读取passwd中的7个域值
read -r 不过滤转义字符
28.数组定义
(1) name=(value0 value1 ... valuen)
(2) name[0]=value0
    name[1]=value1
    ...
    name[n]=valuen
清除数组
unset aa
aa=

echo ${aa[@]}  或者echo ${aa[*]}    显示数组aa所有元素
echo ${#aa[@]} 或者echo ${#aa[*]}   显示数组aa元素个数
echo ${aa[@]:3}或者echo ${aa[*]:3}  显示数组aa中3个以后的所有元素
echo ${aa[@]:3:2}                   显示数组aa中3个以后的2个元素
echo ${#aa[3]}                      显示数组aa中第4个元素长度


aa=( one two three four five five )
从字符串的前部删除最短的匹配,匹配字串是一个正则表达式.
echo ${aa[@]#f*r}  # one two three five five
                   # 匹配表达式作用于数组所有元素.
                   # 匹配了"four"并把它删除.

字符串前部最长的匹配
 echo ${aa[@]##t*e} # one two four five five
                    # 匹配表达式作用于数组所有元素.
                    # 匹配"three"并把它删除.

字符串尾部的最短匹配
 echo ${aa[@]%h*e} # one two t four five five
                   # 匹配表达式作用于数组所有元素.
                   # 匹配"hree"并把它删除.
字符串尾部的最长匹配
 echo ${aa[@]%%t*e} # one two four five five
                    # 匹配表达式作用于数组所有元素.
                    # 匹配"three"并把它删除.
第一个匹配的子串会被替换
 echo ${aa[@]/fiv/XYZ} # one two three four XYZe XYZe
                       #匹配表达式作用于数组所有元素.

所有匹配的子串会被替换
 echo ${aa[@]//iv/YY} # one two three four fYYe fYYe
                      # 匹配表达式作用于数组所有元素.

删除所有的匹配子串,没有指定代替字串意味着删除
 echo ${aa[@]//fi/} # one two three four ve ve
                    # 匹配表达式作用于数组所有元素.

替换最前部出现的字串
 echo ${aa[@]/#fi/XY} # one two three four XYve XYve
                      # 匹配表达式作用于数组所有元素.
替换最后部出现的字串
 echo ${aa[@]/%ve/ZZ} # one two three four fiZZ fiZZ
                      # 匹配表达式作用于数组所有元素.

复制一个数组.
 array2=( "${array1[@]}" )  或 array2="${array1[@]}"

给数组增加一个元素.
 array=( "${array[@]}" "new element" ) 或 array[${#array[*]}]="new element"


如果数组存放的是变量,显示数组中变量内容方法:
echo ${!aa[0]}
eval echo  $(echo \$${aa[0]})
eval echo  \$${aa[0]}
29.bash 执行命令的优先级:
1.别名
2.关键字
3.函数
4.内置命令
5.脚本或可执行程序($PATH)
30.文件代码
fd<>文件         开启文件并指定文件代码为fd
exec 6<>文件        开启文件并指定文件代码为6

fd<&-    关闭文件代码fd
exec 6<&-  关闭文件代码6

n<&m      复制转向输入的文件文件代码m,存成n,使n连接至m
n>&m      复制转向输出的文件文件代码m,存成n,使n连接至m