一篇彻底搞懂-->shell脚本

shell初识

什么是shell?

Shell 是一个用 C 语言编写的程序,它是用户使用 Linux 的桥梁。Shell 既是一种命令语言,又是一种程序设计语言。
Shell 是指一种应用程序,一种和内核沟通的外壳应用程序的统称。这个应用程序有时提供了一个界面,用户通过这个界面访问操作系统内核的服务。

shell脚本开发环境

Shell 编程跟 java、php、python编程一样,只要有一个能编写代码的文本编辑器和一个能解释执行的脚本解释器就可以了。
Linux 的 Shell 解释器种类众多,常见的有: Bourne Shell(/usr/bin/sh或/bin/sh)Bourne Again Shell(/bin/bash)C Shell(/usr/bin/csh)K Shell(/usr/bin/ksh)Shell for Root(/sbin/sh)等
在linux中可以通过cat /etc/shells查看linux系统支持的shell的类型

[root@iZ8vbhcpwdmnwpx91dy1h8Z ~]# cat /etc/shells
/bin/sh
/bin/bash
/usr/bin/sh
/usr/bin/bash

如图就是Centos 7所支持的shell类型
我们关注的是 Bash,也就是 Bourne Again Shell,由于易用和免费,Bash 在日常工作中被广泛使用。同时,Bash 也是大多数Linux 系统默认的 Shell。

第一个Shell脚本

#!/bin/bash/
echo"hello shell!"

#! 是一个约定的标记,它告诉系统这个脚本需要什么解释器来执行,即使用哪一种 Shell。通常,(#!)的名称,叫做”Shebang”或者”Sha-bang”。
echo 命令用于向窗口输出文本。
注意:

如果脚本未指定shebang ,脚本执行的时候,默认用当前shell去解释脚本,即SHELL
如果 shebang 指定了可执行的解释器,如/bin/bash /usr/bin/python ,脚本在执行时,文件名会作为参数传递给解释器
如果#!指定的解释程序没有可执行权限,则会报错"Permission denied”。
如果#!指定的解释程序不是一个可执行文件,那么指定的解释程序会被忽略,转而交给当前的SHELL去执行这个脚本。
如果#!指定的解释程序不存在,那么会报错No such file or directory"。
#!之后的解释程序,需要写其绝对路径(如:#!/bin/bash),它是不会自动到$PATH中寻找解释器的。如果你使用""bash test.sh"这样的命令来执行脚本,那么#!这一行将会被忽略掉,解释器当然是用命令行中显式指定的bash。

但是该怎么样执行shell脚本呢?直接./吗?

[root@iZ8vbhcpwdmnwpx91dy1h8Z ~]# ./hello.sh
-bash: ./hello.sh: Permission denied

可以看到报错,因为没有执行权限,所以加一个执行权限chomd +x hello.sh

root@iZ8vbhcpwdmnwpx91dy1h8Z ~]# chmod +x hello.sh
[root@iZ8vbhcpwdmnwpx91dy1h8Z ~]# ./hello.sh
hello shell

可以看到可以执行hello.sh 脚本了
注意:一定要写成 ./hello.sh,而不是 hello.sh,运行其它二进制的程序也一样,直接写 hello.sh,linux 系统会去 PATH 里寻找有没有叫 test.sh 的,而只有 /bin, /sbin, /usr/bin,/usr/sbin 等在 PATH 里,你的当前目录通常不在 PATH 里,所以写成 hello.sh 是会找不到命令的,要用 ./test.sh 告诉系统说,就在当前目录找。
除了./执行还可以/bin/bash或者/bin/sh执行

[root@iZ8vbhcpwdmnwpx91dy1h8Z ~]# /bin/sh hello.sh
hello shell

这种运行方式是,直接运行解释器,其参数就是 shell 脚本的文件名,这种方式运行的脚本,不需要在第一行指定解释器信息,写了也没用。
Shell脚本中用#表示注释,相当于C语言的//注释。
但如果#位于第一行开头,并且是则例外,它表示该脚本使用后面指定的解释器/bin/bash解释执行。

解释执行本质原理

shell执行本质就是程序替换,这在前边进程控制已经介绍过了,就不再介绍了,这里就简单说一说执行过程吧。

第一种执行方式: chmod +x first.sh Shell会fork一个子进程并调用exec执行./firsh.sh这个程序,exec系统调用应该把子进程的代码段替换成./first.sh程序的代码段,并从它的_start开始执行
然而first.sh是个文本文件,根本没有代码段和_start函数! 怎么办呢? 其实exec还有另外一种机制,如果要执行的是一个文本文件,并且第一行用Shebang指定了解释器,则用解释器程序的代码段替换当前进程,并且从解释器的 _start开始执行,而这个文本文件被当作命令行参数传给解释器。
交互Shell(bash)fork/exec一个子Shell(sh)用于执行脚本,父进程bash等待子进程sh终止
sh读取脚本中的cd …命令,调用相应的函数执行内建命令,改变当前工作目录为上一级目录
sh读取脚本中的ls命令,fork/exec这个程序,列出当前工作目录下的文件,sh等待ls终止
ls终止后,sh继续执行,读到脚本文件末尾,sh终止。
sh终止后,bash继续执行,打印提示符等待用户输入。

现象
先看一段脚本代码

#!/bin/bash

pwd
cd ..
pwd

这段脚本代码时先显示现在所处的路径接着返回到路径的上一层接着又获取现在所处的路径

[root@iZ8vbhcpwdmnwpx91dy1h8Z ~]# ./hello.sh
hello shell!

结果显示返回了当前路径的上一层

[root@iZ8vbhcpwdmnwpx91dy1h8Z ~]# ./test.sh
/root
/
[root@iZ8vbhcpwdmnwpx91dy1h8Z ~]# pwd
/root

但是又一次执行pwd显示路径没有改变,为什么呢?这是因为shell脚本在执行时创建子进程,让子进程解释shell脚本但是看一下下边的情况

[root@iZ8vbhcpwdmnwpx91dy1h8Z ~]# pwd
/root
[root@iZ8vbhcpwdmnwpx91dy1h8Z ~]# cd ../
[root@iZ8vbhcpwdmnwpx91dy1h8Z /]# pwd
/

这里为什么路径改变了?因为在执行命令时不一定创建子进程,执行这些命令并不需要创建子进程,这些叫做shell的内置命令,由父bash亲自执行,理解上,将该类命令,理解成shell的内部函数即可。
在看一个现象

[root@iZ8vbhcpwdmnwpx91dy1h8Z ~]# source test.sh
/root
/
[root@iZ8vbhcpwdmnwpx91dy1h8Z /]# pwd
/

在./或者source执行脚本时影响到了父bash。这是因为source和./时shell脚本的内建命令,这种方式在执行时不会创建子进程,而是直接在 交互式shell下执行脚本中的命令
那为什么子进程解释脚本不会影响父bash?
在shell脚本中,在运行 命令时会复制当前的shell环境并新建一个子shell环境。子shell环境有自己独立的 工作目录(pwd),继承原先shell环境中的alias和function。所以在执行cd …只是改变的是子进程自己的工作目录,并不影响父bash的工作目录。

shell变量

shell是弱类型语言,原则上,不是特别强调shell变量,或者shell变量可以放很多常见内容,这点和传统的C/C++有很大不同。shell变量也不需要提前定义,或者不牵扯到定义一说,需要时直接使用即可。

赋值和命名规则

name=“sigui”

注意,变量名和等号之间不能有空格! 否则会被Shell解释成命令和命令行参数。 同时,变量名的命名须遵循如下规则:

首个字符必须为字母(a-z,A-Z)。
中间不能有空格,可以使用下划线(_)。
不能使用标点符号。
不能使用bash里的关键字(可用help命令查看保留关键字)。

另外,shell变量可以放入你想放入的很多内容

#!/bin/bash
name="sigui"
echo "sigui"
age="25"
echo $name
echo $age[root@iZ8vbhcpwdmnwpx91dy1h8Z /]# source test.sh
sigui
sigui
25

所有的变量不需要先定义后使用,而是直接使用。

使用变量

如上,使用一个赋值过的变量,只要在变量名前面加美元符号即可 如

echo $name

但是有时候,会有这样的问题

#!/bin/bash

name="hello shell"

echo "$nameNIHAO"
[root@iZ8vbhcpwdmnwpx91dy1h8Z ~]# ./test.sh


如果想输出一个hello shellNiHAO但是shell将name和后边的字符串解释为一个变量名,因为这个变量没有定义所以输出了空,但时还是想输出这是该怎么办?

#!/bin/bash
name="hello shell
echo "${name} NIHAO"

[root@iZ8vbhcpwdmnwpx91dy1h8Z ~]# ./test.sh
hello shell NIHAO

只需将变量名加一个{}加花括号是为了帮助解释器识别变量的边界

已定义的变量,可以被重新定义,如:

your_hobby="code"
echo $your_hobby
your_hobby="sleep"
echo $your_hobby

这样写是合法的,但注意,第二次赋值的时候不能写$your_hobby="sleep" 当变量作为右值时,才需要带上$ 符号。

只读变量

使用 readonly 命令可以将变量定义为只读变量,只读变量的值不能被改变

#!/bin/bash

readonly name="hello shell"

echo "${name} NIHAO"

name="hello word!" #对定义的变量重新定义
[root@iZ8vbhcpwdmnwpx91dy1h8Z ~]# ./test.sh
hello shell NIHAO
./test.sh: line 7: name: readonly variable

可以看到name时只读的不能对其修改

删除变量

使用 unset 命令可以删除变量。

#!/bin/bash

name="hello shell"

echo "${name} NIHAO"
unset name
echo "${name} NIHAO"
[root@iZ8vbhcpwdmnwpx91dy1h8Z ~]# ./test.sh
hello shell NIHAO
 NIHAO

结果显示本来输出两行hello shell NIHAO只输出了一行unset后边的name内容没有输出,被删除的变量,内容会被清空,一般也是不在被使用的变量需要unset

unset 命令不能删除只读变量

#!/bin/bash

readonly name="hello shell"

echo "${name} NIHAO"
unset name
echo "${name} NIHAO"
[root@iZ8vbhcpwdmnwpx91dy1h8Z ~]# ./test.sh
hello shell NIHAO
./test.sh: line 6: unset: name: cannot unset: readonly variable
hello shell NIHAO

结果显示只读数据无法被删除

变量类型

变量分为本地变量、环境变量、shell变量
本地变量: 局部变量在脚本或命令中定义,仅在当前shell实例中有效,其他shell启动的程序不能访问局部变量。
环境变量: 所有的程序,包括shell启动的程序,都能访问环境变量,有些程序需要环境变量来保证其正常运行。必要的时候shell脚本也可以定义环境变量。
shell变量: shell变量是由shell程序设置的特殊变量。shell变量中有一部分是环境变量,有一部分是局部变量,这些变量保证shell的正常运行
先看一个例子:

#!/bin/bash
echo "${name}"
[root@iZ8vbhcpwdmnwpx91dy1h8Z ~]# ./test.sh

[root@iZ8vbhcpwdmnwpx91dy1h8Z ~]# name="hello shell"
[root@iZ8vbhcpwdmnwpx91dy1h8Z ~]# ./test.sh

[root@iZ8vbhcpwdmnwpx91dy1h8Z ~]# echo $name
hello shell

在父bash上定义一个变量name但是子进程没有获取变量但是父bash自己可以获取

[root@iZ8vbhcpwdmnwpx91dy1h8Z ~]# export name
[root@iZ8vbhcpwdmnwpx91dy1h8Z ~]# ./test.sh
hello shell

将name写进环境变量中子进程可以获取到

本地变量

只存在于当前Shell进程,用set命令可以显示当前Shell进程中定义的所有变量(包括本地变量和环境变量)和函数。上面的name在没有被export 的时候,就是父shell内的一个本地变量,环境变量是任何进程都有的概念,而本地变量是Shell特有的概念
##环境变量**
环境变量可以从父进程传给子进程,因此Shell进程的环境变量 可以从当前Shell进程传给fork出来的子进程。用printenv命令可以显示当前Shell进程的环境变量。
一个变量定义后仅存在于当前Shell进程,它是本地变量,用export命令可以把本地变量导出为环境变量,定义和导出环境变量通常可以一步完成:
直接导入到环境变量

export name="shell"

或者先定义变量,再导入到环境变量

name="shell"
esport name

用unset命令可以删除已定义的环境变量或本地变量.

unset name

拼接字符串

原则上,只要将信息写在一起,就完成了string的拼接,当然,有一些特殊的地方,我们一一来学习一下:

[root@iZ8vbhcpwdmnwpx91dy1h8Z ~]# cat test.sh
#!/bin/bash


str1="hello"
str2=" shell"[root@iZ8vbhcpwdmnwpx91dy1h8Z ~]# ./test.sh
hello shell NIHAO
echo $str1$str2" NIHAO"

获取字符串长度

#!/bin/bash
str1="hello"

echo ${#str1}
[root@iZ8vbhcpwdmnwpx91dy1h8Z ~]# ./test.sh
5

提取子字符串

从字符串第2个字符开始截取4个字符:

#!/bin/bash
str1="abcd123456789"

echo ${str1:1:4}#前闭后开(]
[root@iZ8vbhcpwdmnwpx91dy1h8Z ~]# ./test.sh
bcd1

查找子字符串

[root@iZ8vbhcpwdmnwpx91dy1h8Z ~]# cat test.sh
#!/bin/bash
string="bit is a great company"
echo `expr index "$string" is`
[root@iZ8vbhcpwdmnwpx91dy1h8Z ~]# ./test.sh
2

文件名代换 (Globbing )** : ? []

这些用于匹配的字符称为通配符(Wildcard),具体如下:

通配符 * : 匹配0个或多个任意字符
? : 匹配一个任意字符
[若干字符] : 匹配方括号中任意一个字符的一次出现

例如创建100个文件

[root@iZ8vbhcpwdmnwpx91dy1h8Z shell]# for i in [1..100]; do touch file$ii ; done
[root@iZ8vbhcpwdmnwpx91dy1h8Z shell]# ls file*
file     file14  file20  file27  file33  file4   file46  file52  file59  file65  file71  file78  file84  file90  file97
file1    file15  file21  file28  file34  file40  file47  file53  file6   file66  file72  file79  file85  file91  file98
file10   file16  file22  file29  file35  file41  file48  file54  file60  file67  file73  file8   file86  file92  file99
file100  file17  file23  file3   file36  file42  file49  file55  file61  file68  file74  file80  file87  file93
file11   file18  file24  file30  file37  file43  file5   file56  file62  file69  file75  file81  file88  file94
file12   file19  file25  file31  file38  file44  file50  file57  file63  file7   file76  file82  file89  file95
file13   file2   file26  file32  file39  file45  file51  file58  file64  file70  file77  file83  file9   file96

其中[1…100]就是匹配出现再1-100之间出现的字符每次字符只能使用一次
注意,Globbing所匹配的文件名是由Shell展开的,也就是说在参数还没传给程序之前已经展开了, 比如上述ls file[1-5]命令,如果当前目录下有file1~file5,则传给ls命令的参数实际上是这5个文件名,而不是一个匹配字符串。

命令代换和算术代换

由反引号``括起来的也是一条命令,Shell先执行该命令,然后将输出结果立刻代换到当前命令行中。

#!/bin/bash
date=`date +%y:%m:%d`
echo ${date}

[root@iZ8vbhcpwdmnwpx91dy1h8Z ~]# ./test.sh
22:04:20


命令代换也可以用 ( ) 表 示 ‘ ‘ ‘ D A T E = () 表示```DATE= ()DATE=(date +%Y:%m:%d)```
(()) 中的shell变量取值将转换成整数,常用于算术计算,例如:

[root@iZ8vbhcpwdmnwpx91dy1h8Z ~]#  cat test.sh
#!/bin/bash
myint=100
echo $myint
(( myint++ ))
echo $myint
[root@iZ8vbhcpwdmnwpx91dy1h8Z ~]#  ./test.sh
100
101

如果要对运算结果进行赋值或者作为右值,需要:

[root@iZ8vbhcpwdmnwpx91dy1h8Z ~]#  cat test.sh
#!/bin/bash
myint=100
echo $myint
res=$(( ++myint ))
echo $myint
echo $res
[root@iZ8vbhcpwdmnwpx91dy1h8Z ~]#  ./test.sh
100
101
101

(()) 中只能用±*/和()运算符,并且只能做整数运算。

转义字符

和C语言类似,\在Shell中被用作转义字符,用于去除紧跟其后的单个字符的特殊意义(回车除外),换句话说,紧跟其后的字符取字面值。另外,\还可以紧跟其后的普通字符取特殊含义。
比如创建和删除一个文件名为$ $ (中间有空格)的文件可以这样:

[root@iZ8vbhcpwdmnwpx91dy1h8Z ~]# touch \$\ \$
[root@iZ8vbhcpwdmnwpx91dy1h8Z ~]# ls
$ $
[root@iZ8vbhcpwdmnwpx91dy1h8Z ~]# ls
$ $
[root@iZ8vbhcpwdmnwpx91dy1h8Z ~]# rm \$\ \$

另外,还有一个字符虽然不具有特殊含义,但是要用它做文件名也很麻烦,就是- 号。如果要创建一个文 件名以- 号开头的文件,这样是不行的: 即使加上\ 转义也还是报错: 因为各种UNIX命令都把- 号开头的命令行参数当作命令的选项,而不会当作文件名。如果非要处理以- 号开头的文件名,可以有两种办法:

[root@iZ8vbhcpwdmnwpx91dy1h8Z ~]# touch -- -file
[root@iZ8vbhcpwdmnwpx91dy1h8Z ~]# touch -- ./-file.bak
[root@iZ8vbhcpwdmnwpx91dy1h8Z ~]# ls 
-file -file.bak
[root@iZ8vbhcpwdmnwpx91dy1h8Z ~]# rm -- -file
[root@iZ8vbhcpwdmnwpx91dy1h8Z ~]# rm -- ./-file.bak

\还有一种用法,在\后敲回车表示续行,Shell并不会立刻执行命令,而是把光标移到下一行,给出 一个续行提示符>,等待
用户继续输入,最后把所有的续行接到一起当作一个命令执行。例如:

[root@iZ8vbhcpwdmnwpx91dy1h8Z ~]# while :;\
> do \
> echo "hello world"; \
> sleep 1; \
> done
hello world
hello world
hello world

单引号双引号

和C语言不一样,Shell脚本中的单引号和双引号一样都是字符串的界定符,而不是字符的界定符。单引号用于保持引号内所有字符的字面值,即使引号内的\和回车也不例外, 但是字符串中不能出现单引号。如果引号没有配对就输入回车,Shell会给出续行提示符,要求用户把引号配上对。
例子:

[root@iZ8vbhcpwdmnwpx91dy1h8Z ~] cat test.sh
#!/bin/bash
mystring="good"
echo "hello shell $mystring \\ \" \\ \` `date +%Y:%m:%d`"
echo 'hello shell $mystring \\ \" \\ \` `date +%Y:%m:%d`'
[root@iZ8vbhcpwdmnwpx91dy1h8Z ~] ./test.sh
hello shell good \ " \ ` 2022:04:20
hello shell $mystring \\ \" \\ \` `date +%Y:%m:%d`

双引号用于保持引号内所有字符的字面值(回车也不例外),但以下情况除外:

$ 加变量名可以取变量的值
反引号仍表示命令替换
$表示$的字面值
`表示`的字面值(反引号)
\"表示"的字面值
\表示\的字面值 除以上情况之外,在其它字符前面的\无特殊含义,只表示字面值

shell脚本语法

条件测试

测试变量是否被定义

shell脚本中测试是用命令来完成的,常见的测试命令包含test 或[ ,通过检查该类命令的退出码,决定条件测试是否成立,如:

[root@iZ8vbhcpwdmnwpx91dy1h8Z ~]# test -v x
[root@iZ8vbhcpwdmnwpx91dy1h8Z ~]# echo $?
1
[root@iZ8vbhcpwdmnwpx91dy1h8Z ~]#  x=1
[root@iZ8vbhcpwdmnwpx91dy1h8Z ~]# test -v x
[root@iZ8vbhcpwdmnwpx91dy1h8Z ~]# echo $?
0
[root@iZ8vbhcpwdmnwpx91dy1h8Z ~]# [ -v x ]
[root@iZ8vbhcpwdmnwpx91dy1h8Z ~]# echo $?
0


没有定义了x,检测x时显示状态码为1,定义x后检查x显示状态码为0.切记,shell认为: 退出码为0,测试条件成立,非0,测试条件不成立。这点和传统语言不同![ 也可以用来检查但是中间要有空格隔开,后边都使用的是[检查

测试数值

整数检测:

-eq 是否等于
-ne 是否不等于not equal
-lt 是否小于less than
-gt 是否大于great than
-le 是否小于等于less equal
-ge 是否大于等于great equal

[root@iZ8vbhcpwdmnwpx91dy1h8Z ~]# [ 1 -eq 100 ];echo $?
1
[root@iZ8vbhcpwdmnwpx91dy1h8Z ~]# [ 1 -lt 100 ];echo $?
0
[root@iZ8vbhcpwdmnwpx91dy1h8Z ~]# [ 1 -le 100 ];echo $?
0
[root@iZ8vbhcpwdmnwpx91dy1h8Z ~]# [ 1 -ge 100 ];echo $?
1

test 和[ 都可以用来进行测试,两者使用稍有不同,虽然看起来很奇怪,但左方括号[ 确实是一个命令的名字,传给命令的各参数之间应该用空格隔开(为什么?),比如, $VAR -gt 3 ] 是[ 命令的四个参数,它们之间必须用空格隔开。命令test或[的参数形式是相同的,只不过test命令不需要]参数。我们后面统一使用[ 来进行测试

测试字符串

==(=) 是否相等
!= 是否不相等
-z 是否为空串
-n是否为非空串

[root@iZ8vbhcpwdmnwpx91dy1h8Z ~]# [ -n "abce" ];echo $?
0
[root@iZ8vbhcpwdmnwpx91dy1h8Z ~]# [ -z "abce" ];echo $?
1
[root@iZ8vbhcpwdmnwpx91dy1h8Z ~]# [ "abce"=="abcd" ];echo $?
0
[root@iZ8vbhcpwdmnwpx91dy1h8Z ~]# [ "abce"!="abcd" ];echo $?
0

看一下下边的情况:

#!/bin/bash

read str
[ str == "abc" ]
echo $?

[root@iZ8vbhcpwdmnwpx91dy1h8Z ~]# ./test.sh#直接回车没有输入

./test.sh: line 4: [: str: unary operator expected
2

原因在于:

#!/bin/bash -x

read str
[ $str == "abc" ]
echo $?
[root@iZ8vbhcpwdmnwpx91dy1h8Z ~]# ./test.sh
+ read str

+ '[' $str == abc ']'#左边为空了
./test.sh: line 4: [: str: unary operator expected
+ echo 2
2

怎么解决?

#!/bin/bash -x

read str
[ "X$str" == "abc" ]
echo $?

推荐写法

测试文件

d
-f
-b
-c

[root@iZ8vbhcpwdmnwpx91dy1h8Z ~]# cat test.sh
#!/bin/bash
[ -c /dev/tty ] #字符设备
echo $?
[ -b /dev/sda ] #块设备
echo $?
[ -f ./test.sh ] #普通文件
echo $?
[ -d / ] #目录
echo $?
[root@iZ8vbhcpwdmnwpx91dy1h8Z ~]# ./test.sh
0
0
0
0

再次强调,命令test 或[ 可以测试一个条件是否成立,如果测试结果为真,则该命令的Exit Status为0,如果测试结果为假,则命令的Exit Status为1(注意与C语言的逻辑表示正好相反)

多条件测试

和C语言类似,测试条件之间还可以做与、或、非逻辑运算,带与、或、非的测试命令格式如下:
逻辑反

[root@iZ8vbhcpwdmnwpx91dy1h8Z ~]# cat test.sh
#!/bin/bash
read mystring
[ ! "X$mystring" == "Xhello" ]
echo $?
[root@iZ8vbhcpwdmnwpx91dy1h8Z ~]# ./test.sh
false
0

逻辑与

[root@iZ8vbhcpwdmnwpx91dy1h8Z ~]# cat test.sh
#!/bin/bash
printf "Please enter data1: "
read data1
printf "Please enter data2: "
read data2
[ $data1 -eq 100 -a $data2 -eq 200 ]
echo $?
[root@iZ8vbhcpwdmnwpx91dy1h8Z ~]# ./test.sh
Please enter data1: 100
Please enter data2: 200
0
[root@iZ8vbhcpwdmnwpx91dy1h8Z ~]# ./test.sh
Please enter data1: 100
Please enter data2: 201
1

逻辑或

[root@iZ8vbhcpwdmnwpx91dy1h8Z ~]#cat test.sh
#!/bin/bash
printf "Please enter data1: "
read data1
printf "Please enter data2: "
read data2
[ $data1 -eq 100 -o $data2 -eq 200 ]
echo $?
[root@iZ8vbhcpwdmnwpx91dy1h8Z ~]# ./test.sh
Please enter data1: 100
Please enter data2: 200
0
[root@iZ8vbhcpwdmnwpx91dy1h8Z ~]# ./test.sh
Please enter data1: 100
Please enter data2: 201
0
[root@iZ8vbhcpwdmnwpx91dy1h8Z ~]# ./test.sh
Please enter data1: 200
Please enter data2: 300
1
[root@iZ8vbhcpwdmnwpx91dy1h8Z ~]# ./test.sh
Please enter data1: 200
Please enter data2: 200
0

脚本控制

上面的若干条件判断命令,只能判断出条件真或者假,但是在实际应用中,判断出真假只是第一步,我们还要根据
判断结果来进行语句分流。
和C语言类似,在Shell中用if、then、elif、else、fi这几条命令实现分支控制。这种流程控制语句本质上也是由若干
条Shell命令组成的, 先来看条件判断的实例:

第一个shell脚本控制程序

#!/bin/bash

read str

if [ "X$str" == "Xhello" ]
then
    echo "get hello"
fi

[root@iZ8vbhcpwdmnwpx91dy1h8Z ~]# ./test.sh
hello
get hello

如果两条命令写在同一行则需要用;号隔开,一行只写一条命令就不需要写;号了,另外,then后面有换行,但这条命令没
写完,Shell会自动续行,把下一行接在then后面当作一条命令处理。和[命令一样,要注意命令和各参数之间必须用空
格隔开。所以上面的代码还可以这样写

#!/bin/bash
printf "please Enter String:"
read str

if [ "X$str" == "Xhello" ];then
    echo "get hello"
fi
[root@iZ8vbhcpwdmnwpx91dy1h8Z ~]# ./test.sh
please Enter String:hello
get hello

编写一个在文件数据中进行字符串匹配的脚本

[root@iZ8vbhcpwdmnwpx91dy1h8Z ~]# cat file
1111111111111111111111111111111111111111111111111111111111111111111111111111111111111111
2222222222222222222222222222222222222222222222222222222222222222222222222222222222222222
1111111111111111111111111111111111111111111111111111111111111111111111111111111111111111
abc2222222222222222222222222222222222222222222222222222222222222222222222222222222222222

#!/bin/bash
printf "please Enter file name:"
read str

if grep -Eq "abc" "$str";then
    echo "yes"
else
    echo "no"
fi

[root@iZ8vbhcpwdmnwpx91dy1h8Z ~]# ./test.sh
please Enter file name:file
yes

显示匹配成功
用if 来进行条件判断,直接判断grep 命令的执行结果。
-E : 使用扩展正则匹配
-q : 使用安静模式匹配
Shell脚本没有{}括号,所以用fi表示if语句块的结束。

空代码块

#!/bin/bash
printf "please Enter file name:"
read str

if [ 100 -eq 100 ];then
    
fi
[root@iZ8vbhcpwdmnwpx91dy1h8Z ~]# ./test.sh
please Enter file name:100
./test.sh: line 7: syntax error near unexpected token `fi'
./test.sh: line 7: `fi'
[root@iZ8vbhcpwdmnwpx91dy1h8Z ~]# cat test.sh


如果在代码块中,出现了空余句情况,什么都不写,shell会直接报错。那怎么解决呢?
:是一个特殊的命令,称为空命令,该命令不做任何事,但Exit Status总是真。此外,也可以执行/bin/true或/bin/false得到真或假的Exit Status。 如:

#!/bin/bash
printf "please Enter file name:"
read str

if [ 100 -eq 100 ];then
   : 
fi

[root@iZ8vbhcpwdmnwpx91dy1h8Z ~]# ./test.sh
please Enter file name:100

在上边的代码中添加了 :就没有报错了

||和&&

此外,Shell还提供了&&和||语法,和C语言类似,具有Short-circuit(懒逻辑)特性,很多Shell脚本喜欢写成这样:
测试程序

#!/bin/bash
printf "please Enter file name:"
read str

[ $str -eq 100 ] && echo "yes"

[ $str -eq 100 ] && {
    echo "yes"
}
[root@iZ8vbhcpwdmnwpx91dy1h8Z ~]# ./test.sh
please Enter file name:100
yes
yes

测试程序二:

[root@iZ8vbhcpwdmnwpx91dy1h8Z ~]# cat test.sh
#!/bin/bash
printf "please Enter file name:"
read str

[ $str -eq 100 ] && echo "yes2"

[ $str -eq 100 ] || {
    echo "yes1"
}
[root@iZ8vbhcpwdmnwpx91dy1h8Z ~]# ./test.sh
please Enter file name:100
yes2
[root@iZ8vbhcpwdmnwpx91dy1h8Z ~]# ./test.sh
please Enter file name:200
yes1

&&相当于“if…then…”,而||相当于“if not…then…”。&&和||用于连接两个命令,而上面讲的-a和-o仅用于在测试表达式中连接两个测试条件,要注意它们的区别。

case/esac

case命令可类比C语言的switch/case语句,esac表示case语句块的结束。C语言的case只能匹配整型或字符型常量表达式,而Shell脚本的case可以匹配字符串和Wildcard,每个匹配分支可以有若干条命令,末尾必须以;;结束,执行时找到第一个匹配的分支并执行相应的命令,然后直接跳到esac之后,不需要像C语言一样用break跳出。来看例子:

#!/bin/bash



case $1 in
        'start')
                echo "start---done"
        ;;
        'finaly')
                echo "finall---done"
        ;;
        'stop')
                echo "stop----done"
        ;;
        'restart')
                echo "restart---done"
        ;;
esac

[root@iZ8vbhcpwdmnwpx91dy1h8Z ~]# ./test.sh stop
stop----done
[root@iZ8vbhcpwdmnwpx91dy1h8Z ~]# ./test.sh restart
restart---done
[root@iZ8vbhcpwdmnwpx91dy1h8Z ~]# ./test.sh start
start---done

$1 是一个特殊变量,在执行脚本时自动取值为第一个命令行参数,运行代码的过程如下
还有一种情况,如果是当有start 或者-s 选项时,输出start … done 这种逻辑时,当如何写?如下:

#!/bin/bash
case $1 in
        'start' | -s)
                echo "start---done"
        ;;
        'finaly' | -f)
                echo "finall---done"
        ;;
        'stop' | -st)
                echo "stop----done"
        ;;
        'restart' | -r)
                echo "restart---done"
        ;;
        *)
                echo "default"
        ;;
esac
[root@iZ8vbhcpwdmnwpx91dy1h8Z ~]# vim test.sh
[root@iZ8vbhcpwdmnwpx91dy1h8Z ~]# ./test.sh start -s 
start---done
[root@iZ8vbhcpwdmnwpx91dy1h8Z ~]# ./test.sh finaly -f 
finall---done
[root@iZ8vbhcpwdmnwpx91dy1h8Z ~]# ./test.sh finaly
finall---done
[root@iZ8vbhcpwdmnwpx91dy1h8Z ~]# ./test.sh
default

在修改一下首字母可以大小写都可以识别

#!/bin/bash



case $1 in
        [Ss]tart | -s)
                echo "start---done"
        ;;
        [Ff]inaly | -f)
                echo "finall---done"
        ;;
        [Ss]top | -st)
                echo "stop----done"
        ;;
        [Rr]estart | -r)
                echo "restart---done"
        ;;
        *)
                echo "default"
        ;;
esac


[root@iZ8vbhcpwdmnwpx91dy1h8Z ~]# ./test.sh Start
start---done
[root@iZ8vbhcpwdmnwpx91dy1h8Z ~]# ./test.sh start
start---done
[root@iZ8vbhcpwdmnwpx91dy1h8Z ~]# ./test.sh Finaly
finall---done
[root@iZ8vbhcpwdmnwpx91dy1h8Z ~]# ./test.sh finaly
finall---done

这样首字母大小写都可以识别

循环控制语句

for循环

Shell脚本的for循环结构和C语言很不一样, 但是他有类似于C的写法,先来看看这种:

#!/bin/bash
for ((i=0; i<=10; i++))
do
echo "hello $i"
done

其中(( )) 这个结构我们之前讲过,在该结构中,所有的运算和C是一样的。
当然,shell有它个性的循环方式(其实,很多脚本语言都有类似循环),它类似于某些编程语言的foreach循环。例
如:

#!/bin/bash
for i in {1,2,3,4}
do

done

字符串遍历

[root@iZ8vbhcpwdmnwpx91dy1h8Z ~]# 
#!/bin/bash
for i in {A..z}
do
	sleep 0.01; echo "$i";
done
A
B
C
D
E
F
G
H
I
J
K
L
M
N
O
P
Q
R
S

[root@iZ8vbhcpwdmnwpx91dy1h8Z ~]# cat ctl.sh
#!/bin/bash
for i in {a..e} {1..5}
do
echo "hello $i"
done
[root@iZ8vbhcpwdmnwpx91dy1h8Z ~]# ./ctl.sh
hello a
hello b
hello c
hello d
hello e
hello 1
hello 2
hello 3
hello 4
hello 5

总之一句话, for in 循环功能很强大,有些时候能够简化我们的逻辑,后面出场率还是很高的。

while循环

#!/bin/bash
i=0
while [ $i -le 100 ]
do
        ((i++))
        #let i++
        echo "$i "
        
done

循环输出1-100 符合循环条件就是i小于100,不过while循环,一定要注意负责进行索引的自增

until循环

这个循环是shell脚本特有的

#!/bin/bash
i=0
until [ $i -ge 100 ]
do
        ((i++))
        echo "$i "
done

这个循环也是打印1-100之间但是这和while循环不同的是中间的条件检测,while循环和C语言差不多都是符合条件就循环进入循环,而until且相反只有符合条件就直接跳出循环

死循环

#!/bin/bash
for (( ; ; ))
do
	echo "hello bit"
done
#!/bin/bash
while :
do
	echo "hello bit"
done
或者
while true
do
	echo "hello bit"
done
#!/bin/bash
until false
do
echo "hello bit"
done
#!/bin/bash
until false
do
echo "hello bit"
done

另外,如果我的循环体里什么都没有呢?
这里和if哪里相同也是直接加:就可以了

until false
do
	:
done

命令行循环

命令行循环就是在命令行上写基本得循环结构

[root@iZ8vbhcpwdmnwpx91dy1h8Z ~]# for i in {A..z} {1..100};do sleep 0.01; echo "$i"; done

简单shell脚本测试

求1~100的求和,用shell脚本编写。

#!/bin/bash
i=0
ret=0
for i in {1..100}
do
        ((ret=i+ret))
        
done      
echo "sum is $ret"

[root@iZ8vbhcpwdmnwpx91dy1h8Z ~]# bash 1.sh
sum is 5050

可如果我们要求你打出执行过程,出现类似1+2+3+4…+100=5050 这种结果呢?

#!/bin/bash
i=0
ret=0
string=" "
for i in {1..100}
do      
        ((ret=i+ret))
        if [ -z "$string" ];then
                string=$i
        else 
                string=$string"+"$i
        fi
done
echo "$string=$ret"
[root@iZ8vbhcpwdmnwpx91dy1h8Z ~]# bash 1.sh
 +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+42+43+44+45+46+47+48+49+50+51+52+53+54+55+56+57+58+59+60+61+62+63+64+65+66+67+68+69+70+71+72+73+74+75+76+77+78+79+80+81+82+83+84+85+86+87+88+89+90+91+92+93+94+95+96+97+98+99+100=5050
	

求100以内所有的奇数之和

#!/bin/bash
i=0
ret=0
while [ $i -le 100 ]
do
        let ret=i+ret
        let i=i+2 
done      
echo "sum=$ret"

位置参数和特殊变量

有很多特殊变量是被Shell自动赋值的,我们已经遇到了$? 和$1 ,现在总结一下,常用的位置参数和特殊变量

$0 : 相当于C语言main函数的argv[0], 还有$1、$2… ,这些称为位置参数(Positional Parameter),相当于C语言main函数 的argv[1],argv[2]…
$# : 相当于C语言main函数的argc - 1 ,注意这里的#后面不表示注释
$@ : 表示参数列表$1 $2 … ,例如可以用在for循环中的in后面。
$? : 上一条命令的Exit Status
$$ : 当前Shell的进程号

[root@iZ8vbhcpwdmnwpx91dy1h8Z ~]# cat test.sh
#!/bin/bash
echo "\$0 -> $0"
echo "\$1 -> $1"
echo "\$2 -> $2"
echo "\$3 -> $3"
echo "\$# -> $#"
echo "\$@ -> $@"
echo "\$? -> $?"
[root@iZ8vbhcpwdmnwpx91dy1h8Z ~]# ./test.sh arg1 arg2 arg3
$0 -> ./test.sh
$1 -> arg1
$2 -> arg2
$3 -> arg3
$# -> 3
$@ -> arg1 arg2 arg3
$? -> 0

shift

位置参数可以用shift命令左移。比如shift 3 表示原来的$4 现在变成$1 ,原来的$5 现在变成$2 等 等,原来的$1 、$2 、$3 丢弃, $0 不移动。不带参数的shift命令相当于shift 1。例如:

[root@iZ8vbhcpwdmnwpx91dy1h8Z ~]#  cat test.sh
#!/bin/bash
echo "###############shift 1 before####################"
echo "\$0 -> $0"
echo "\$1 -> $1"
echo "\$2 -> $2"
echo "\$3 -> $3"
echo "\$# -> $#"
echo "\$@ -> $@"
echo "\$? -> $?"
echo "###############shift 1 after####################"
shift 1
echo "\$0 -> $0"
echo "\$1 -> $1"
echo "\$2 -> $2"
echo "\$3 -> $3"
echo "\$# -> $#"
echo "\$@ -> $@"
echo "\$? -> $?"
[root@iZ8vbhcpwdmnwpx91dy1h8Z ~]#  ./test.sh arg1 arg2 arg3
###############shift 1 before####################
$0 -> ./test.sh
$1 -> arg1
$2 -> arg2
$3 -> arg3
$# -> 3
$@ -> arg1 arg2 arg3
$? -> 0
###############shift 1 after####################
$0 -> ./test.sh
$1 -> arg2
$2 -> arg3
$3 ->
$# -> 2
$@ -> arg2 arg3
$? -> 0

如何遍历命令行参数

[root@iZ8vbhcpwdmnwpx91dy1h8Z ~]#  cat test.sh
#!/bin/bash
for i in $@
do
echo $i
done
[root@iZ8vbhcpwdmnwpx91dy1h8Z ~]#  ./test.sh arg1 arg2 arg3
arg1
arg2
arg3
[root@iZ8vbhcpwdmnwpx91dy1h8Z ~]# cat test.sh
#!/bin/bash
while [ $# -ne 0 ]
do
echo $1
shift 1
done
[root@iZ8vbhcpwdmnwpx91dy1h8Z ~]# ./test.sh arg1 arg2 arg3
arg1
arg2
arg3

函数

和C语言类似,Shell中也有函数的概念,但是函数定义中没有返回值也没有参数列表。
简单的函数测试程序

在定义fun()函数时并不执行函数体中的命令,就像定义变量一样,只是给fun这个名字一个定义, 到后面调用fun函数的时候(注意Shell中的函数调用不写括号)才执行函数体中的命令。

#!/bin/bash
function fun()
{
        echo "I am function"
}
fun

[root@iZ8vbhcpwdmnwpx91dy1h8Z ~]# bash 2.sh
I am function

Shell脚本中的函数必须先定义后调用,一般把函数定义都写在脚本的前面,把函数调用和其它命令写在脚本的最后(类似C语言中的main函数,这才是整个脚本实际开始执行命令的地方)

函数传参

传参时,将shell函数当成更小的脚本。
Shell函数没有参数列表并不表示不能传参数,事实上,函数就像是迷你脚本,调用函数时可以传任意个参数,在函数内同样是用$1 、$2 等变量来提取参数。

#!/bin/bash


function fun(){
	echo $0	 //第一个参数
	echo $1  //第二额参=个数
	echo $2 //第三个参数
	echo $# //参数个数
	echo $@ //参数列表
	
}
fun  arg1 arg2 arg3

[root@iZ8vbhcpwdmnwpx91dy1h8Z ~]# bash 1.sh
1.sh
arg1
arg2
3
arg1 arg2 arg3

试验表明, $0 并不会作为函数参数,从事传参任务。
测试二

#!/bin/bash

echo "#############function#################"
function fun(){
	echo $0
	echo $1
	echo $2
	echo $#
	echo $@
	
}
fun  arg1 arg2 arg3
echo "################shell##############"

	echo $0
	echo $1
	echo $2
	echo $#
	echo $@
echo "###################################"

[root@iZ8vbhcpwdmnwpx91dy1h8Z ~]# bash 1.sh
#############function#################
1.sh
arg1
arg2
3
arg1 arg2 arg3
################shell##############
1.sh


0

###################################

函数中的位置参数相当于函数的局部变量,改变这些变量并不会影响函数外面的$1 、$2 等变量
可以说函数中变量$1 $2 和shell脚本中的是不一样的

函数返回值

在shell脚本中函数和其他面向对象语言一样函数都有返回值,但是面向对象函数的得返回值都是由变量获取,但是shell脚本中函数返回值是由 ? 获 取 , 函 数 调 用 或 者 返 回 时 , 将 s h e l l 函 数 当 成 命 令 。 只 要 是 命 令 , 那 么 函 数 调 用 成 功 与 否 , 可 以 通 过 ?获取,函数调用或者返回时,将shell函数当成命令。只要是命令,那么函数调用成功与否,可以通过 ?shell?来判定。一般函数中可以用return命令返回,如果return后面跟一个数字则表示函数的退出状态。 如下:

#!/bin/bash

echo "#############function#################"
function fun(){
	echo $0
	echo $1
	echo $2
	echo $#
	echo $@
	return 123
}
fun  arg1 arg2 arg3
echo "函数退出状态"$?
[root@iZ8vbhcpwdmnwpx91dy1h8Z ~]# bash 1.sh
#############function#################
1.sh
arg1
arg2
3
arg1 arg2 arg3
函数退出状态123

后边可以根据函数退出码判断函数是不是成功运行

#!/bin/bash

echo "#############function#################"
function fun(){
	echo $0
	echo $1
	echo $2
	echo $#
	echo $@
	return 123
}
fun  arg1 arg2 arg3
if [ $? -eq 123 ];then
	echo "function sunccess"
else
	echo"function fail"
fi

[root@iZ8vbhcpwdmnwpx91dy1h8Z ~]# bash 1.sh
#############function#################
1.sh
arg1
arg2
3
arg1 arg2 arg3
function sunccess

总结:

函数调用或者返回时,将shell函数当成命令
命令的执行结果是否正确,可以通过该命令的退出码判定
shell if条件判断认为,0为真,非0为假
if条件判断是根据命令的退出码判断的。[ test 都是命令

上边代码还可以直接改写为下边的代码

#!/bin/bash

echo "#############function#################"
function fun(){
	
	return 0
}
if fun;then
	echo "function sunccess"
else
	echo"function fail"
fi

[root@iZ8vbhcpwdmnwpx91dy1h8Z ~]# bash 1.sh
#############function#################
function sunccess

函数返回值为0,在if条件判断时直接判断函数返回值是不是0就可以判断函数是不是成功运行

echo方式

函数返回值除了上边的方式意外还有echo,换句话说,就是不关心函数的退出码,而关心他的数据string。

#!/bin/bash

function fun(){
        echo "success"
}
ret=$(fun)  #变量拿到函数返回值下边进行判断函数有没有成功运行
if [ "X$ret" == "Xsuccess" ] ;then
        echo "function sunccess"
else
        echo"function fail" 
fi        
[root@iZ8vbhcpwdmnwpx91dy1h8Z ~]# bash 1.sh
function sunccess

这种方式也是可以的,注意其中的$() , 这种方式,要注意的就是,函数里面最终有且只能有一条string输出。

shell脚本调试方法

shell脚本本身,调试没有C/C++那么多的调试方式或者工具,一般我们常规的方法,shell都支持。但是主要通过一些调试选项来进行。

-n: 读一遍脚本中的命令但不执行,用于检查脚本中的语法错误
-v: 一边执行脚本,一边将执行过的脚本命令打印到标准错误输出
-x: 提供跟踪执行信息,将执行的每一条命令和结果依次打印出来

使用这些选项,一般有三种方法
一是在命令行使用

[root@iZ8vbhcpwdmnwpx91dy1h8Z ~]# bash -x test.sh
+ test tmp
++ myfun
++ echo success
+ ret=success
test.sh: line 14: syntax error near unexpected token `fi'
test.sh: line 14: `fi'

二是在脚本开头提供参数

#! /bin/sh -x

三是在脚本中用set命令启用或禁用参数
set -x和set +x分别表示开启和禁用-x参数,这样可以只对脚本中的某一段进行跟踪调试。

[root@iZ8vbhcpwdmnwpx91dy1h8Z ~]# cat test.sh
#!/bin/bash
echo "hello 1"
echo "hello 2"
echo "hello 3"
echo "hello 4"
set +x
echo "hello 5"
echo "hello 6"
echo "hello 7"
set -x
echo "hello 8"
echo "hello 9"
echo "hello 10"
[root@iZ8vbhcpwdmnwpx91dy1h8Z ~]# ./test.sh
hello 1
hello 2
hello 3
hello 4
hello 5
hello 6
hello 7
+ echo 'hello 8'
hello 8
+ echo 'hello 9'
hello 9
+ echo 'hello 10'
hello 10

数组

数组存放的时同一类型得多个数据的集合,shell脚本只支持一维数组,再初始化时并不需要定义数组的大小也并没有限制数组的大小,数组下标也是从0开始,获取元素也要利用下标回去数组中的元素,下标可以是整数或算术表达式,其值应大于或等于0
Shell数组用括号来表示,元素用"空格"符号分割开,可以不使用连续的下标,而且下标的范围没有限制。,语法格
式如下:

shell_array=(value1,value2.....valuen)
或者
shell_array[0]=""
shell_array[1]=""
shell_array[2]=""

数组元素读取

${shell_arrray[index]}

定义一个数组实现循环遍历数组

#!/bin/bash +x

ret=${#nums[@]} #获取数组长度
ret=${#nums[*]} #获取数组长度
nums=(1 "abc" 0.123 'ABC' '*')
i=0
while [ $i -le  $ret ] 
do 
	echo"$i->${nums[i]}"
	let i+=1
done

shell与文件

大多数 linux 系统命令从你的终端接受输入并将所产生的输出发送回 到你的终端。一个命令通常从一个叫标准输入的地方读取输入,默认情况下,这恰好是你的终端。同样,一个命令通常将其输出写入到标准输出,默认情况下,这也是你的终端。
C/C++当中,读取文件需要相关函数来进行完成,但shell就简单多了

shell输出重定向

[root@iZ8vbhcpwdmnwpx91dy1h8Z ~]# touch file
[root@iZ8vbhcpwdmnwpx91dy1h8Z ~]# ls
file
[root@iZ8vbhcpwdmnwpx91dy1h8Z ~]# echo "hello shell" > file
[root@iZ8vbhcpwdmnwpx91dy1h8Z ~]# cat file
hello shell

在.sh文件中循环往file文件中输出重定向

root@iZ8vbhcpwdmnwpx91dy1h8Z ~]# cat 1.sh
#i/bin/bash
for i in {1..10}
do
	echo "hello shell"$i
done	> file

[root@iZ8vbhcpwdmnwpx91dy1h8Z ~]# cat file
hello shell1
hello shell2
hello shell3
hello shell4
hello shell5
hello shell6
hello shell7
hello shell8
hello shell9
hello shell10

shell脚本追加重定向

#!/bin/bash
for i in {1..10}
do
	echo "hello shell $i" >> file
done
[root@iZ8vbhcpwdmnwpx91dy1h8Z ~]# cat file
end
[root@iZ8vbhcpwdmnwpx91dy1h8Z ~]# vim 1.sh
[root@iZ8vbhcpwdmnwpx91dy1h8Z ~]# bash 1.sh
[root@iZ8vbhcpwdmnwpx91dy1h8Z ~]# cat file
end
hello shell 1
hello shell 2
hello shell 3
hello shell 4
hello shell 5
hello shell 6
hello shell 7
hello shell 8
hello shell 9
hello shell 10

shell脚本输入重定向

向shell脚本中输入数据

[root@iZ8vbhcpwdmnwpx91dy1h8Z ~]# cat file
end
hello shell 1
hello shell 2
hello shell 3
hello shell 4
hello shell 5
hello shell 6
hello shell 7
hello shell 8
hello shell 9
hello shell 10
[root@iZ8vbhcpwdmnwpx91dy1h8Z ~]# cat 1.sh
#!/bin/bash
read line < file
echo $line
[root@iZ8vbhcpwdmnwpx91dy1h8Z ~]# bash 1.sh
end

进行文件多行读入

[root@iZ8vbhcpwdmnwpx91dy1h8Z ~]# cat file
end
hello shell 1
hello shell 2
hello shell 3
hello shell 4
hello shell 5
hello shell 6
hello shell 7
hello shell 8
hello shell 9
hello shell 10
#!/bin/bash


while read line
do
        echo $line
done < file
[root@iZ8vbhcpwdmnwpx91dy1h8Z ~]# bash 1.sh
end
hello shell 1
hello shell 2
hello shell 3
hello shell 4
hello shell 5
hello shell 6
hello shell 7
hello shell 8
hello shell 9
hello shell 10

如果想给每行添加字符串NIHAO ,在备份到文件file_t 呢?

[root@iZ8vbhcpwdmnwpx91dy1h8Z ~]# cat file
end
hello shell 1
hello shell 2
hello shell 3
hello shell 4
hello shell 5
hello shell 6
hello shell 7
hello shell 8
hello shell 9
hello shell 10
[root@iZ8vbhcpwdmnwpx91dy1h8Z ~]# cat 1.sh
#!/bin/bash


while read line
do
	echo $line" NIHAO">>file_t
done < file
#还可以这样写
[root@iZ8vbhcpwdmnwpx91dy1h8Z ~]# cat 1.sh
#!/bin/bash


while read line
do
	echo $line" NIHAO"
done < file >>file_t
[root@iZ8vbhcpwdmnwpx91dy1h8Z ~]# cat file_t
end NIHAO
hello shell 1 NIHAO
hello shell 2 NIHAO
hello shell 3 NIHAO
hello shell 4 NIHAO
hello shell 5 NIHAO
hello shell 6 NIHAO
hello shell 7 NIHAO
hello shell 8 NIHAO
hello shell 9 NIHAO
hello shell 10 NIHAO

默认情况下,command > file 将 stdout 重定向到 file,command < file 将stdin 重定向到 file。
如果希望 stderr(标准错误) 重定向到 file.txt,可以这样写:


[root@iZ8vbhcpwdmnwpx91dy1h8Z ~]# find a
find: ‘a’: No such file or directory
[root@iZ8vbhcpwdmnwpx91dy1h8Z ~]# find a 2> file.txt
[root@iZ8vbhcpwdmnwpx91dy1h8Z ~]# cat file.txt
find: ‘a’: No such file or directory

如果希望 stderr 追加到 file.txt 文件末尾,可以这样写:

[root@iZ8vbhcpwdmnwpx91dy1h8Z ~]# find b
find: ‘b’: No such file or directory
[root@iZ8vbhcpwdmnwpx91dy1h8Z ~]# find b 2>>file.txt
[root@iZ8vbhcpwdmnwpx91dy1h8Z ~]# cat file.txt
find: ‘a’: No such file or directory
find: ‘b’: No such file or directory

2 表示标准错误文件(stderr)。

如果希望将 stdout 和 stderr 合并后重定向到 file.txt,可以这样写:

[root@iZ8vbhcpwdmnwpx91dy1h8Z ~]# command > file 2>&1
或者
[root@iZ8vbhcpwdmnwpx91dy1h8Z ~]# command >> file 2>&1
2>&1:表示标准错误输出重定向等同于标准输出,因为之前标准输出已经重定向到了空设备文件,所以标准错误输出也重定向到空设备文件。

command代表的是linux命令

shell脚本特殊重定向(Here Document)

Here Document 是 Shell 中的一种特殊的重定向方式,用来将输入重定向到一个交互式 Shell 脚本或程序。 它的基本的形式如下

command << delimiter
		document
delimiter

它的作用是将两个 delimiter 之间的内容(document) 作为输入传递给 command。

结尾的delimiter 一定要顶格写,前面不能有任何字符,后面也不能有任何字符,包括空格和 tab 缩进。
开始的delimiter前后的空格会被忽略掉。

例子:

#!/bin/bash


cat > file <<EOF
        abc
        abc
        abc
EOF
[root@iZ8vbhcpwdmnwpx91dy1h8Z ~]# cat file
	abc
	abc
	abc

将EOF之间得内容重定向到文件file中
shell脚本自动生成Makefile文件

!/bin/bash


cat > makefile <<EOF  #或者cat  < makefile
test:test.c
	gcc -o test test.c
.PHONY:clean
clean:
	rm -f test
EOF

[root@iZ8vbhcpwdmnwpx91dy1h8Z ~]# cat makefile
test:test.c
	gcc -o test test.c
.PHONY:clean
clean:
	rm -f test

/dev/null 文件

如果希望执行某个命令,但又不希望在屏幕上显示输出结果,那么可以将输出重定向到 /dev/null:

我们通常把Linux下的/dev/null看作"黑洞"。它非常等价于一个只写文件。所有写入它的内容都会永远丢失。而尝试从它那儿读取内容则什么也读不到. 然而, /dev/null对命令行和脚本都非常的有用。

/dev/null 属于字符特殊文件,它属于空设备,是一个特殊的设备文件,它会丢弃一切写入其中的数据,写入它的内容都会永远丢失,而且没有任何可以读取的内容。所以我们一般会把/dev/null当成一个垃圾站,不要的东西丢进去。比如来清除文件中的内容。

# command > /dev/null

/dev/null 是一个特殊的文件,写入到它的内容都会被丢弃;如果尝试从该文件读取内容,那么什么也读不到。但是 /dev/null 文件非常有用,将命令的输出重定向到它,会起到"禁止输出"的效果。 如果希望屏蔽 stdout 和stderr,可以这样写:

[root@iZ8vbhcpwdmnwpx91dy1h8Z ~]# cat makefile 2>&1 > /dev/null
[root@iZ8vbhcpwdmnwpx91dy1h8Z ~]# cat makefile
test:test.c
	gcc -o test test.c
.PHONY:clean
clean:
	rm -f test

没有显示makefile内容
清空文件数据

[root@iZ8vbhcpwdmnwpx91dy1h8Z ~]# cat /dev/null > makefile
[root@iZ8vbhcpwdmnwpx91dy1h8Z ~]# cat makefile

/dev/null 2>&1 这条命令的意思就是在后台执行这个程序,并将错误输出2重定向到标准输出1,然后将标准输出1全部到/dev/null文件,也就是清空.所以可以看出常用来避免shell命令或者程序等运行中有内容输出
禁止标准输出

cat $file >/dev/null

禁止标准输出和标准错误的输出

cat $file (文件)2>/dev/null >/dev/null
# 如果"$file"不存在,将不会有任何错误信息提示.
# 如果"$filen"存在, 文件的内容不会打印到标准输出.

shell与信号

shell也可以用来处理信号,常见的方式如下:

trap ‘commands’ signal-list, 当脚本收到signal-list清单内列出的信号时,trap命令执行引号中的命令.
trap signal-list, trap不指定任何命令,接受信号的默认操作.默认操作是结束进程的运行.
trap ‘’ signal-list, trap命令指定一个空命令串,允许忽视信号.

#!/bin/bash

trap 'echo "hello shell";tree' 2
while :
do
        :
done

[root@iZ8vbhcpwdmnwpx91dy1h8Z ~]# bash 1.sh
^Chello shell
.
├── 1.sh
└── makefile

0 directories, 2 files
^Chello shell
.
├── 1.sh
└── makefile

收到2号信号就会执行trap命令执行引号中的命令tree

shell文件包含

shell也可以做成类似C/C++那样的文件包含的样式,这样可以很方便的封装一些公用的代码作为一个独立的文件。
一般有下面两种方式:

.
source

先编写一个shell脚本api也就是一个独立的函数

[root@iZ8vbhcpwdmnwpx91dy1h8Z ~]# cat api.sh
#!/bin/bash


function fun() {
	let data=$1+$2
	echo $data
	tree
}

先求和在执行tree

函数调用

[root@iZ8vbhcpwdmnwpx91dy1h8Z ~]# cat 2.sh
#! /bin/sh

#先执行api加载api中的函数
source api.sh
read l1 l2
# 函数调用

ret=$(fun l1 l2)
echo $ret
[root@iZ8vbhcpwdmnwpx91dy1h8Z ~]# bash 2.sh
10 20
30 . ├── 2.sh ├── api.sh └── makefile 0 directories, 3 files

shell运算符

具体介绍参考自这里

echo & printf

echo

echo  后边跟字符串
例如加引号
echo "It is a test"
也可以不加引号
echo It is a test
显示换行
# echo -e "hello! \nworld"
hello!
world
也可以不换行
# cat test.sh
#!/bin/bash
echo -e "hello!\c"
echo "world"
# ./test.sh
hello!world

printf

printf 命令模仿 C 程序库(library)里的 printf() 程序。 标准所定义,因此使用printf的脚本比使用echo移植性好。 printf 使用引用文本或空格分隔的参数,外面可以在printf中使用格式化字符串,还可以制定字符串的宽度、左右对齐方式等。默认printf不会像echo自动添加换行符,我们可以手动添加 \n。 printf 命令的语法:

printf format-string [arguments...]
[root@iZ8vbhcpwdmnwpx91dy1h8Z ~]# cat test.sh
#!/bin/bash
mystring='string'
myint=100
myfloat=3.14
printf "%s : %d : %f\n" $mystring $myint $myfloat
[root@iZ8vbhcpwdmnwpx91dy1h8Z ~]# ./test.sh
string : 100 : 3.140000

产生一个随机数

echo $RANDOM

你可能感兴趣的:(linux,linux,shell脚本,shell编程,脚本)