Shell基本

shell:是一种命令解释器还是一种编程语言(shell脚本,shell编程),擅长处理文本类型的数据,所以Shell脚本在管理Linux系统中发挥了巨大作用
备份文件、安装软件、下载数据之类的事情,学着使用sh,bash会是一个好主意.

1
2
3
4
5
6
#!/bin/bash
# "#!"是一个约定的标记,它告诉系统这个脚本需要什么解释器来执行
# shell脚本会被分解成一行一行的依次执行.
# bash shell会将空格,制表符,换行符当作字段分隔符
IFS=$'\n' # 只识别换行符
IFS=$'\n:;"' # 将换行,冒号,分号和双引号作为字段分隔符

脚本文件没有可执行权限时

1
sh test.sh

脚本有可执行权限

1
2
3
4
5
6
7
8
9
./test.sh
# 注意,一定要写成./test.sh,而不是test.sh,运行其它二进制的程序也一样,
# 直接写test.sh,linux系统会去PATH里寻找有没有叫test.sh的,
# 而只有/bin, /sbin, /usr/bin,/usr/sbin等在PATH里,你的当前目录通常不在PATH里
# 所以写成test.sh是会找不到命令的,要用./test.sh告诉系统说,就在当前目录找
source 或.
# 命令执行与上面执行不同,上面执行 shell 都会分配一个子进程来进行
# source 或. 命令就在本进程执行,也就是说一些非环境变量也能读取到

重定向输入和输出:将某个命令的输出重定向到另一个位置(文件,或者命令)

命令行的数据流定义:STDIN(0),STDOUT(1),STDERR(2),用来管理SHELL中的信息流的分流.

重定向输出:将命令的输出发到一个文件中

1
2
3
4
5
STDOUT: >(覆盖) >>(追加) 重定向到文件
STDERR: 2>
STDOUT STDERR: 2>&1
ehco "alias ll='ls -alF'" >> test.txt
date > test.txt

重定向输入:将文件的内容重定向到命令中

1
2
3
grep femn < /etc/passwd
grep femn < /etc/passwd
echo `grep femn < /etc/passwd`

管道命令:将一个集合的输出做会一个命令的输入 STDOUT,STDERR | STDIN

1
rpm -qa | sort | more

生成已安装包的列表,经过管理排序,再经过管理传给More命令显示

Shell基本类型

赋值与引用:
脚本编程中的一个主要构件 反引号 ( `` ) 它允许你将shell命令的输出赋给变量

1
2
3
4
5
testing=`date +%y%m%d` #你必须用把整个命令行命令圈起来
$HOME ${HOME}
echo # -n 在同一行显示一个文本字符串作为命令输出.
echo -e "OK! \n" # -e 开启转义
/$15 # 转义符

变量类型只能为Int,str,数组,而且等于两边不能有空格

数字
在将一个数学运算结果赋值给一个变量时,你可以用美元符和方括号将数字表达式圈起来

1
2
$[option]
$(())

注意只支持整数运算

1
2
3
var=1
var1=$[2 * 5]
var1=$(($var*5))

字符串

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
var=femn
greeting="hello, ${var} !"
var="my name"
echo ${#var} #获取字符串长度
echo ${var:1:4} #字符串分片
if [-z str 为0 ]
# -z 字符串的长度为零则为真,退出状态为0.
if [-n str 非0]
# -n str 字符串的长度不为零则为真
var=http://www.aaa.com/123.htm
1. # 号截取,删除左边字符,保留右边字符.
echo ${var#*//} # www.aaa.com/123.htm
2. ## 号截取,删除左边字符,保留右边字符
echo ${var##*/} # 123.htm
3. %号截取,删除右边字符,保留左边字符
echo ${var%/*} # http://www.aaa.com
4. %% 号截取,删除右边字符,保留左边字符
echo ${var%%/*} # http:
5. 从左边第几个字符开始,及字符的个数
echo ${var:0:5} # http:
6. 从左边第几个字符开始,一直到结束
echo ${var:7} # www.aaa.com/123.htm
7. 从右边第几个字符开始,及字符的个数
echo ${var:0-7:3} # 123
8. 从右边第几个字符开始,一直到结束
echo ${var:0-7} #123.htm

数组

1
2
3
4
list=(1 2 3 4)
echo ${list[0]} #
echo ${list[@]} #获取数组中所有的元素
echo ${#list[@]} # 计算数组个数

shell语法

结构化命令允许你改变程序执行的顺序,在某些条件下执行一些命令而在其它条件下跳过另一些命令.

if-then语句,只能测试跟命令的退出状态码有关的条件.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
1.if command:then
commands
fi
# 使用一行,多用于终端
if [ $(ps -ef | grep -c "ssh") -gt 1 ]; then echo "true"; fi
# 如果else分支没有语句执行,就不要写这个else.
2.if command1
then
commands
else
commands
fi
# 使用一行,多用于终端
if [ $(ps -ef | grep -c "ssh") -gt 1 ]; then echo "true"; \
else echo 'false'; fi
3.if command1
then
commands
elif command2
then
commands
fi

case命令,替代elif语句来不断检查相同变量值.case命令提供了一个更清晰的方法来为变量每个可以的值指定不同的选项.

1
2
3
4
5
6
7
8
case $USER in
femn | pattern2 )
commands1;;
parrent3)
commands2;;
*)
commands;;
esac

test命令会将所有的标点和大小写也考虑在内

在其它编程语言中,if语句之后的对象是一个等式来测试是 true or false .bash shell并不是这样工作的.

当command1命令退出状态码是0,位于then部分的命令就会被执行.如果是非0则执行else命令块.

test命令提供了在if-then语句中测试不同条件的途径.
test命令格式:test condition(要测试的一系列参数和值)

1
2
3
4
5
6
7
# test condition==[ condition ]
# 你必须在左括号右侧和右括号左侧各加一个空格,否则会报错.
if test ${num1} -eq ${num2}
if [ ${num1} -eq ${num2} ]
# 复合条件测试
[ condition1 ] && [ condition2 ]
||

test可以判断3类条件:1.数值比较,2.字符串比较,3.文件比较
数值比较:

1
2
3
4
5
6
-gt: 大于(-g)
-lt: 小于(-l)
-eq: 等于(-e)
-ne: 不等于
-ge: 大于等于
-le: 小于等于

bash shell能处理的数仅有整数.使用bash计算器时,你可以让shell将浮点值作为字符串值存储进一个变量,如果你只是要通过echo语句来显示这个结果,那它能很好地工作,便它无法在基于数字的函数中工作.

字符串比较:

1
2
3
4
5
6
<
>
=
!=
-z
-n

字符串顺序:要测试一个字符串要比另一个字符串大就开始变得烦琐了,大于小于符号必须转义,否则shell会把它们当做重写向符号而把字符串值当做文件名.而且大于小于顺序和sort命令所采用的不同.test命令中大写字母会被当成小于小写字母.
if [Test \< test]

文件比较:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# 检查new.sh文件是否比old.sh新,在此之前要先确认文件是否存在.
if [ ./new.sh -nt ./old.sh ]
if [ ./old.sh -ot ./new.sh ] #
if [ -e $HOME ] #检查文件是否存在 下面的所有检查都要满足这个前提条件
if [ -d $HOME ] # 检查文件是否存在并是个目录
if [ -f $HOME/.vimrc ] # 并是个文件
if [ ! -f $HOME/.vimrc ] # 如果不是文件则执行
if [ -r $HOME ] # 并是可读
if [ -w $HOME/.vimrc ] # 并是可写
if [ -x $HOME/.vimrc ] # 并是可执行
if [ -O $HOME/.vimrc ] # 并是 属于当前用户所有
if [ -s $HOME/.vimrc ] # 并是不为空,说明有数据,删除时就小心了.

if-then的高级特征:

双圆括号命令允许你将高级数学表达式放入比较中

1
(( expression ))

test命令只允许在比较中进行简单的算术操作.双圆括号命令提供了更多的为用过其它编程语言的程序员所熟悉的数学符号.你不需要将圆括号中表达式里的大于号转义,这是双圆括号命令提供的另一个高级特性.

用于高级字符串处理功能的双方括号. [[ expression ]]

1
2
3
4
if (( $var1 ** 2 > 90 )) || [[ $USER == f* ]]
# 幂运算之后比较大小
# 匹配$USER是否以f字母开头
# 模式匹配中,你可以定义一个正则表达式来匹配字符串值.

循环中定义的test命令和if-then语句中定义的是一样的格式.

你需要重复一组命令直到达到某个特定条件.for, while, until
for循环假定每个值都是用空格分割的

1
2
3
4
5
6
7
8
9
for var in list
do
commands
done
for file in `ls /etc`
for loop in 1 2 3 4 5
for var in item1 item2 ... itemN; do command1; command2… done;
for test in I don\'t know if "this'll " work

用通配符读取目录:你可以你for命令来自动遍历满是文件的目录.进行操作时,你必须在文件名或路径名使用通配符.它会强制shell命令使用文件扩展匹配(file globbing):它是生成匹配指定的通配符的文件名或路径外的过程.

1
2
3
4
5
6
7
8
9
10
11
12
13
for file in $HOME/*
do
if [-d "$file"]
# 为了解决含有空格的目录名或文件名,将$file变量用双引号圈起来
then
echo "$file is a directory"
elif [-f "$file"]
then
echo "$file is a file"
else
echo "$file is doesn't exist"
fi
done

C语言的for命令:通常会定义一个变量,然后这个变量会在每次迭代时自动改变.程序员会将这个变量当作计数器,并在每次迭代中让计数器增一或减一.
这是bash中C语言风格的for循环的基本格式:for((variable assignment;condition;iteration process))

1
2
3
4
5
6
7
8
9
10
11
for (i=0;i<10;i++)
{
print("the next number is %d\n",i)
}
for ((a=1,b=10;a<10;a++,b--))
do
echo "$a - $b"
done
# 使用多个变量时,循环会单独处理每个变量,
# 允许你为每个变量定义不同的迭代过程,但只能定义一种条件.

while
while命令在某种意义上是if-then语句和for循环的混杂体.它会在每个迭代的一开始测试test命令.只有测试条件成立,while命令才会继续遍历执行定义好的命令.

1
2
3
4
5
6
7
8
9
10
11
12
while test command
do
other command
done
#!/bin/bash
int=1
while(( $int<=5 ))
do
echo $int
let "int++"
done ))

break终止执行后面的所有循环
continue命令类似,只有一点差别,它不会跳出所有循环,仅仅跳出当前循环

until命令和while命令工作的方式完全相反
测试条件中用到的变量必须被修改,否则会进入一个无限循环.
嵌套循环nested loop:可以在循环内使用任何类型的命令,自然也包括其它循环命令.在使用nested loop时,你是在迭代中使用迭代,命令运行的次数是乘积关系.内部循环会有外部循环的每次迭代中遍历一遍它所有的值.
以两个for命令写个nested loop

shell变量

bash shell提供了不同的方法从用户处获得数据,包括命令行参数,命令行选项以及直接从键盘读取输入的能力.

命令行参数(添加在命令后的数据值)
  1. $?专属变量来保存上个执行命令的退出状态码(0:命令成功结束,126:没有执行权限,1:无效参数等通用未知错误)

  2. $1-$9 第一到第九个命令行参数

  3. ${10} # 第十个命令行参数的读取方式

当传给$0变量的真实字符串是整个脚本路径时,程序中就会使用整个路径,而不仅仅是程序名.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
vim tesh.sh
#!/bin/bash
name=`basename $0`
# basename命令仅仅会返回程序名
echo the command entered is $name
echo the command entered is $0
sh ./test.sh
# the command entered is test.sh
# the command entered is ./test.sh
sh ~/test.sh
# the command entered is test.sh
# the command entered is /home/femn/test.sh
# 检查命令行参数中是否有数据,有的话就执行then语句
if [ -n "$1" ]
# 所有命令行参数的个数
if [ $# -ne 2 ]
# $*和$@变量提供了对所有参数的快速访问
# $*变量将会把所有参数当成单个参数
for param in "$*"
# $@变量会单独处理每个参数
for param in "$@"

shell 命令

bash shell工具链中另一个工具是shift命令.bash shell提供了shell命令来帮助操作命令行参数.
默认情况下,shell命令会将参数变量减一,所以变量$2会移到$1,而变量$1的值会被删除并且无法恢复
变量$0是程序名不会改变.这是遍历命令行参数的另一个绝妙方法,尤其是你不知道有多少参数时.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
vim test1.sh
#!/bin/bash
# demonstration the shift command
count=1
while [ -n "$1" ]
do
echo "Parameter #$count = $1"
count=$(( $count+1 ))
shift
done
sh ./test1.sh femn leipengkai yuyu
# Parameter #1 = femn
# Parameter #2 = leipengkai
# Parameter #3 = yuyu

命令行选项(是跟在单破折线后面的单个字母,可以修改命令行为的单字母值)

当脚本看到”–” ,脚本会安全地将剩下的命令行参数当做参数来处理而不是选项.
你可以像处理命令行参数一样处理命令行选项
你经常遇到想在脚本中同时使用选项和参数的情况.
Linux处理这个问题的标准方式是用特殊的字符将二者分开,该字符(–双破折线)会告诉脚本选项何时结束,以及普通参数何时开始.

getopt命令

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
getopt -q ab:cd -a -b test1 -cde test2 test3
# 用getopt 命令生成的格式化后的版本来替换已有的命令行选项和参数
# -a -b 'test1' -c -d -- 'test2' 'test3'
vim test2.sh
#!/bin/bash
# extraction command line options and values.
while [ -n "$1" ]
do
case "$1" in
-a)
echo "Found the -a options";;
-b) param="$2"
echo "Found the -b options,with parameter value $param"
shift;;
-c)
echo "Found the -c options";;
--) shift
break;;
*) echo "$1 is not an optionso";;
esac
shift
done
count=1
for param in "$@"
do
echo "Parameter #$count: $param"
count=$(( $count +1 ))
done
sh ./test2.sh -b test2 -a -d
# Found the -b options,with parameter value test2
# Found the -a options
# -d is not an optionso
sh ./test2.sh -b test2 -a -b
# Found the -b options,with parameter value test2
# Found the -a options
# Found the -b options,with parameter value
# ./test2.sh: 18: shift: can't shift that many

你可以在脚本中使用getopt命令来格式化输入脚本的任何命令行选项和参数,但用起来略微复杂.

1
set -- `getopt -q ab:cd "$@" ` options parameters用"$@"

该方法将原本的脚本的命令行参数传给getopt命令,之后再将getopt的输出传给set命令,用getopt命令格式化后的命令行参数来替换原始命令行参数.

set 命令的选项之一是双破折线,它会将命令行参数替换成set命令的命令行的值.

1
2
3
4
5
6
7
8
9
10
11
12
13
sh ./test2.sh -ac
# 合并选项 增加下面这句
set -- `getopt -q ab:c "$@"`
while [ -n "$1" ]
# Found the -a options
# Found the -c options
sh ./test2.sh -a -b test1 -cd test2 test3
# Found the -a options
# Found the -b options,with parameter value 'test1'
# Found the -c options
# Parameter #1: 'test2'
# Parameter #2: 'test3'

但getopt命令并不擅长处理带空格的参数值,它会将空格当做参数分隔符,而不是根据双引号将二者当作一个参数.

getopts命令扩展了getopt命令的功能:getopts optstring variable

与getopt命令将命令行上找到的选项和参数处理后只生成一个输出不同,getopts命令能够和已有的shell参数变量对应地顺序工作.

getopts命令将当前参数保存在命令行中定义的variable中.它会用到两个环境变量,如果选项需要参数值,OPTARG环境变量就会保存这个值.OPTIND环境变量保存了参数列表中getopts正在处理的参数位置.

获得用户输入

你想在运行脚本时,问一个问题,并等运行脚本的人来回答.bash shell为此提供了read命令.
read命令接受从标准输入(键盘)或另一个文件描述符的输入.
echo -n -n会移除字符串末尾的换行符,允许脚本用户紧跟其后输入数据,而不是下一行
read -n1 -t 5 -p 接受单个字符后退出而不必回车,超时,直接在read命令行提示符
read -s隐藏方式读取,实际上数据会被显示,只是read命令会将文本颜色设成跟背景色一样.

1
2
3
4
5
6
vim test.sh
#!/bin/bash
# echo -n "ENter your name:"
# read name
read -p "ENter your name:" name
echo "Hello $name, Welcome"

从文件中读取.每次调用read命令会从文件中读取一行文本,当文件没能内容时,read命令会退出并返回一个非零的状态码.

最常见的方法是将文件通过cat命令后的输出通过管道直接传给含有read命令的while命令.cat test |while read line

Share Comments