shell的格式


shell可以在直接在命令行下输入,也可以保存成shell脚本文件运行。当命令简单并且不需要重复使用,在命令行输入直接执行即可,否则就写成脚本。shell脚本默认文件扩展名为
.sh。在shell脚本中,写入的内容,会默认当成一条命令来执行。

例如:
#!/bin/bash echo 'hello world'
* 第1行 指定shell脚本的解释器
* 第2行 执行echo命令
将上面的代码存为test.sh,并将可执行权限赋予它chmod +x test.sh ,执行./test.sh运行脚本。

上面的脚本将会输出:

hello world

这和在命令行或者终端模拟器下输入echo 'hello world'并按下回车得到的结果是一样的

注释

和所有的编程语言一样,shell也有注释,在shell中,#号和它后面的内容来表示一个注释:
# Print a message echo "I'm a shell script."
输出内容

echo用于向输出流输出内容,例如:
echo "hello world"
输入内容

read关键字用于输入一条内容:
read input echo $input
上面的代码中,read关键字从输入流读取一个值并赋予input,然后将input的内容打印出来

1. 变量

定义变量和赋值

变量的命名规则和C语言差不多,支持英文字母和下划线。shell中变量名前不需要声明类型,变量名后面不能有空格,例如:
var1='hello' var2=90
读取变量

$后接变量名意为读取一个变量的值,例如:
var="hello" echo $var
也可以用${var}方式访问到变量值,例如:
var="hello" echo ${var}
访问变量的时候$var和${var}是等效的,推荐后者来访问一个变量

变量作用域

全局变量

没有任何关键字修饰的变量是一个全局变量,全局变量在同一个shell会话中都是有效的。可以跨文件
function func(){ a=90 } func echo $a
输出:

90
$ a=90 $ echo ${a} $ bash $ echo ${a}
输出:

90

空值

局部变量

local关键字用于声明一个局部变量
function func(){ local a=90 } func echo $a
输出:

空值

环境变量

用export关键字修饰的变量称为环境变量,在父shell会话中声明一个环境变量,子shell中都可以访问
$ export path="/system/bin" $ bash #创建一个新的shell会话 $ echo ${path}
特殊变量

变量 含义
$0 当前脚本的文件名
$n(n≥1) 传递给脚本或函数的参数。n 是一个数字,表示第几个参数。例如,第一个参数是 $1,第二个参数是 $2
$# 传递给脚本或函数的参数个数
$* 传递给脚本或函数的所有参数
$@ 传递给脚本或函数的所有参数
$? 上个命令的退出状态,或函数的返回值
$$ 当前 Shell 进程 ID。对于 Shell 脚本,就是这些脚本所在的进程 ID
$*和$@的区别

* $*得到所有参数的字符串形式
* $@得到所有参数的数组形式,可以直接遍历
2. 获取一条命令的执行结果

用 ` 将一条命令包裹起来

` 这个符号,在键盘上的位置是在Esc键的下方
ret=${pwd} echo ${ret}
在 ` 包裹起来的命令中,也可以访问到变量
path='/' ret=`ls -l ${path}` echo ${ret}
以$(command)这种方式执行命令
ret=$(pwd) echo ${ret}
用$(command)这种方式也可以访问到变量
path='/' ret=$(ls -l ${path}) echo ${ret}
上面的例子中,如果想打印命令结果中的换行符,则:
path='/' ret=$(ls -l ${path}) echo "${ret}"
$(command)方式来执行命令更加直观,但是要注意,$() 仅在 Bash Shell 中有效,而反引号可在多种 Shell 中都可使用

3. 字符串

Shell有三种方式可以表示字符串

字符串的表示

(1)变量名后直接跟上字符
str=hello echo ${str}
输出:

hello

这种方式的字符串遇到空格就会被终止

(2)单引号
str=hello echo '${str}'
输出:

${str}

单引号里的内容是字符串原始的样子,不存在转义

(3)双引号
str=shell echo "${str}:\"hello wolrd\""
输出:

shell:"hello world"

双引号中可以访问变量和转义

字符串的长度
str="hello" echo ${#str}
字符串拼接

两个变量放在一起访问就可以拼接
a='hello' b='world' c=${a}${b} echo ${c}
输出:

helloworld

也可以这样
echo 'hello'"world"
字符串截取

(1) 从左边开始截取字符串,格式:${string: start :length},length可省略,省略时,是截取到字符串末尾
msg="hello world" echo ${msg: 6: 5}
输出:

world

(2) 在指定位置截取字符

* 截取chars后面的字符:${string#*chars}
其中,string 表示要截取的字符,chars 是指定的字符(或者子字符串),是通配符的一种,表示任意长度的字符串。
chars连起来使用的意思是:忽略左边的所有字符,直到遇见 chars(chars 不会被截取)。

*
截取最后一次出现chars的位置后面的内容:${string##*chars}

*
使用 % 截取左边字符
使用%号可以截取指定字符(或者子字符串)左边的所有字符,具体格式如下:
${string%chars*}

请注意 * 的位置,因为要截取 chars 左边的字符,而忽略 chars 右边的字符,所以 * 应该位于chars的右侧。其他方面%和#的用法相同

4. 运算符和流程控制

基本运算

运算符 作用
+ 加(需要结合expr命令使用)
- 减(需要结合expr命令使用)
* 乘(需要结合expr命令使用)
/ 除(需要结合expr命令使用)
% 求余(需要结合expr命令使用)
= 赋值
== 判断数值是否相等,需要结合[]使用
!= 判断数值是否不相等,需要结合[]使用 a=8 b=4 echo "a=$a,b=$b" var=`expr ${a} + ${b}` echo
"加法结果:${var}" var=`expr ${a} - ${b}` echo "减法结果:${var}" # 注意:乘号需要转义 var=`expr
${a} \* ${b}` echo "乘法结果:${var}" var=`expr ${a} / ${b}` echo "除法结果:${var}"
var=`expr ${a} % ${b}` echo "求余结果:${var}" var=$[${a} == ${b}] echo
"是否相等:${var}" var=$[${a} != ${b}] echo "是否不相等:${var}"
输出:
a=8,b=4 加法结果:12 减法结果:4 乘法结果:32 除法结果:2 求余结果:0 是否相等:0 是否不相等:1
上面的例子中,调用expr命令和使用[],得到表达式的值,并将它们输出

请注意表达式两边的空格,shell中表达式两边要有空格

关系运算

运算符 作用
-eq 全称:Equal,判断两个数是否相等
-ne 全称:Not equal,判断两个数是否不相等
-gt 全称:Greater than,判断前面那个数是否大于后面那个数
-lt 全称:Less than,判断前面那个数是否小于后面那个数
-ge 全称:Greater equal,判断前面那个数是否大于等于后面那个数
-le 全称:Less than,判断前面那个数是否小于等于后面那个数
布尔运算

运算符 作用
! 非运算
-o 或运算
-a 并运算
逻辑运算

运算符 作用
&& 逻辑并
|| 逻辑或
* 用&&连接起来的两个命令,前面的执行失败就不执行后面的命令 cd /bin && ls /bin
其实和C语言中的是差不多的,只要前面的条件不满足,后面那个就不用去执行它了

* 用||连接起来的两个命令,前面的执行失败才会执行后面的命令 cd /bin || ls /bin
在这里顺便说一下;这个运算符,这个运算符用于连接多个语句,使多个语句能够在同一行。用;连接起来的语句,不管前面的执行成不成功,都会执行后面的
mkdir luoye;cd luoye;pwd
文件判断

运算符 作用
-e 判断对象是否存在
-d 判断对象是否存在,并且为目录
-f 判断对象是否存在,并且为常规文件
-L 判断对象是否存在,并且为符号链接
-h 判断对象是否存在,并且为软链接
-s 判断对象是否存在,并且长度不为0
-r 判断对象是否存在,并且可读
-w 判断对象是否存在,并且可写
-x 判断对象是否存在,并且可执行
-O 判断对象是否存在,并且属于当前用户
-G 判断对象是否存在,并且属于当前用户组
-nt 判断file1是否比file2新
-ot 判断file1是否比file2旧
流程控制语句

(1) if语句

if语句格式如下:
if <condition> then #do something elif <condition> then #do something else #do
something fi
如果想把then和if放同一行
if <condition> ; then #do something elif <condition> ; then #do something else
#do something fi
其中elif和else可以省略

例子:
read file if [ -f ${file} ] ; then echo 'This is normal file.' elif [ -d
${file} ] ; then echo 'This is dir' elif [ -c ${file} -o -b ${file} ] ; then
echo 'This is device file.' else echo 'This is unknown file.' fi
逻辑判断也可以用test命令,它和[]的作用是一样的
#!/bin/bash a=4 b=4 if test $[a+1] -eq $[b+2] then echo "表达式结果相等" else echo
"表达式结果不相等" fi
输出:

表达式结果不相等

(2) for 语句

if语句格式如下:
for <var> in [list] do # do something done
例子:
read input for val in ${input} ; do echo "val:${val}" done
输入:

1 2 3 4 5

输出:

val:1

val:2

val:3

val:4

val:5

(3) while 语句
while <condition> do #do something done
例子:
a=1 sum=0 while [ ${a} -le 100 ] ;do sum=`expr ${sum} + ${a}` a=`expr ${a} +
1` done echo ${sum}
输出:

5050

5. 函数

* 用function关键字来定义一个函数
* 直接写一个函数名来调用一个无参数的函数
* 函数有参数,调用时,在函数名后面写上参数,多个参数用空格隔开
* 调用函数时传递参数,在函数体内部,通过 $n的形式来获取参数的值,例如:$1表示第1个参数,$2表示第2个参数...
函数的结构
function foo(){ # do something... }
函数的用法示例
function foo(){ local name=$1 local age=$2 echo "My name is ${name},I'm ${age}
years old." } foo "luoye" 26
输出:

My name is: luoye,I'm 26 years old.

6. 重定向

重定向可以理解把一个东西传送到另个地方

重定向符 作用
output > file 将输出流重定向到文件
output >> file 将输出流追加到文件末尾
input < file 将文件的内容重定向到输入流
输出到文件

例子:
echo 'hello' > out.txt echo 'world' >> out.txt cat out.txt
输出:
hello world
上面的例子,将hello从输出流重定向文件,将world追加到out.txt文件尾,最后用cat命令读取并打印out.txt的文件内容

重定向符还可以配合数字(0,1,2)使用

* 0 代表标准输入流
* 1 代表标准输出流,上面的例子没有指定数字,就是默认输出流
* 2 代表标准错误流 ls / 1> out.txt cat out.txt
执行上面的命令,会将根目录下的文件名和目录名输出到out.txt
ls /luoye 2> out.txt cat out.txt
执行上面的命令,会将执行ls /luoye命令时的错误信息输出到out.txt

特殊文件

/dev/null

所有重定向到这个文件的内容都会消失,常常同于忽略错误输出。
ls /luoye 2> /dev/null
如果不存在/luoye这个目录或者文件,就没有什么提示

/dev/zero

这个文件会不断产出空的数据,该文件常被dd命令使用
dd if=/dev/zero of=out.txt bs=1 count=16
从/dev/zero输入,输出到out.txt,生成一个大小为16字节的空文件

7. 管道


管道是Linux中的一种跨进程通信的机制,和重定向不同,管道用做进程与进程之间传送数据。做为Linux中默认的脚本语言,shell中也是可以使用管道的,在shell中,管道用
|表示

(1)使用管道进行数据筛选内容中包含root的行
ls -l /|grep root
这个例子中,ls命令输出的内容传给了grep命令进行筛选

(2)也可以同时用多个管道

使用多个管道把数据筛选并统计
ls -l /|grep root|wc -l
这个例子中,ls命令输出的内容传给了grep命令进行筛选,然后转给wc命令统计行数。

为了更好的理解管道,写两个脚本来体验一下:

in.sh文件
#! /bin/bash read msg echo "Receive :${msg}"
out.sh文件
#! /bin/bash echo 'hello'
在命令行中执行./out.sh |./in.sh

输出:
Receive :hello
符合我们预期,字符串hello从out.sh传送到了 in.sh!

8. 参考

* http://c.biancheng.net/shell/
* http://www.runoob.com/linux/linux-shell-process-control.html
* https://www.cnblogs.com/qlqwjy/p/8684630.html