Shell学习

Shell既是一种命令语言,有是一种程序设计语言.
它可以交互解释用户输入的命令,也可以有各种变量,参数,控制结构.

类型

几种常见类型的shell

  • bash:是Linux标准默认的shell
  • sh:是Unix标准默认的shell(包括macox)

简单例子

test.sh

1
2
#!/bin/bash
echo "Hello World"

chmod +x ./test.sh #使脚本具有执行权限
./test.sh #执行脚本
sh test.sh #执行脚本

这里要写成./test.sh,而不是test.sh,如果这样写,linux系统回去path中寻找有没有test.sh

学习

read关键字

read可以从stdin获取输入的赋值并且赋值到一个变量

1
2
3
echo "What is your name?"
read PERSON
echo "Hello, $PERSON"

变量

定义变量

vareName=”value”

变量名和等号之间不能有空格

使用变量
1
2
varName="a value"
echo $verName

使用一个定义过的变量,只要在前面加$访问即可
也可以用{}扩住变量,方便让解释器识别变量的边界,推荐使用
用readonly关键字可以定义只读变量
用unset关键字可以删除变量

特殊的变量
  • $0 当前脚本的文件名
  • $n 传递给脚本或函数的参数。n 是一个数字,表示第几个参数。例如,第一个参数是$1,第二个参数是$2。
  • $# 传递给脚本或函数的参数个数。
  • $* 传递给脚本或函数的所有参数。
  • $@ 传递给脚本或函数的所有参数。被双引号(“ “)包含时,与 $* 稍有不同
  • $? 上个命令的退出状态,或函数的返回值。大部分成功返回0,失败返回1.
  • $$ 当前Shell进程ID。对于 Shell 脚本,就是这些脚本所在的进程ID。
$* 和 $@ 的区别

感觉不是很常用的东西,但是还是从学习网站中摘录出来,以备不时之需

$* 和 $@ 都表示传递给函数或脚本的所有参数,不被双引号(“ “)包含时,都以”$1” “$2” … “$n” 的形式输出所有参数。

但是当它们被双引号(“ “)包含时,”$*” 会将所有的参数作为一个整体,以”$1 $2 … $n”的形式输出所有参数;”$@”会将各个参数分开,以”$1” “$2” … “$n” 的形式输出所有参数。

-e 如果echo中有转义符,需要加-e

1
2
3
#!/bin/bash
a=10
echo -e "Value of a is $a \n"

命令替换

命令替换是指Shell可以先执行命令,将输出结果暂时保存,在适当的地方输出。

1
`command`

一个简单的例子

1
2
3
4
5
6
7
#!/bin/bash
DATE=`date`
echo "Date is $DATE"
USERS=`who | wc -l`
echo "Logged in user are $USERS"
UP=`date ; uptime`
echo "Uptime is $UP"
变量替换

根据变量的状态(是否为空,是否定义)来改变它的值

形式 说明
${var} 变量本来的值
${var:-word} 如果变量 var 为空或已被删除(unset),那么返回 word,但不改变 var 的值
${var:=word} 如果变量 var 为空或已被删除(unset),那么返回 word,并将 var 的值设置为 word。
${var:?message} 如果变量 var 为空或已被删除(unset),那么将消息 message 送到标准错误输出,可以用来检测变量 var是否可以被正常赋值。若此替换出现在Shell脚本中,那么脚本将停止运行。
${var:+word} 如果变量 var 被定义,那么返回 word,但不改变 var 的值。

运算

Bash 支持很多运算符,包括算数运算符、关系运算符、布尔运算符、字符串运算符和文件测试运算符。
原生bash不支持简单的数学运算,但是可以通过其他命令来实现,例如 awk 和 expr,expr 最常用。
例如:两个数相加:

1
2
3
#!/bin/bash
val=`expr 2 + 2`
echo "Total value : $val"

注意:

  • 表达式和运算符之间要有空格,例如 2+2 是不对的,必须写成 2 + 2,这与我们熟悉的大多数编程语言不一样。
  • 完整的表达式要被 包含,注意这个字符不是常用的单引号,在 Esc 键下边。
算术运算符
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#!/bin/sh
a=10
b=20
val=`expr $a + $b`
val=`expr $a - $b`
val=`expr $a \* $b`
val=`expr $b / $a`
val=`expr $b % $a`
if [ $a == $b ]
then
echo "a is equal to b"
fi
if [ $a != $b ]
then
echo "a is not equal to b"
fi

注意条件表达式的空格,[ $a == $b ],错误写法:[$a==$b]

关系运算符

关系运算符只支持数字,不支持字符串,除非字符串是数字

运算符 说明 举例
-eq 检测两个数是否相等,相等返回 true。 [ $a -eq $b ] 返回 true
-ne 检测两个数是否相等,不相等返回 true。 [ $a -ne $b ] 返回 true。
-gt 检测左边的数是否大于右边的,如果是,则返回 true。 [ $a -gt $b ] 返回 false。
-lt 检测左边的数是否小于右边的,如果是,则返回 true。 [ $a -lt $b ] 返回 true。
-ge 检测左边的数是否大等于右边的,如果是,则返回 true。 [ $a -ge $b ] 返回 false。
-le 检测左边的数是否小于等于右边的,如果是,则返回 true。 [ $a -le $b ] 返回 true。
布尔运算符
运算符 说明 举例
! 非运算,表达式为 true 则返回 false,否则返回 true。 [ ! false ] 返回 true。
-o 或运算,有一个表达式为 true 则返回 true。 [ $a -lt 20 -o $b -gt 100 ] 返回 true。
-a 与运算,两个表达式都为 true 才返回 true。 [ $a -lt 20 -a $b -gt 100 ] 返回 false。
字符串运算符
运算符 说明 举例
= 检测两个字符串是否相等,相等返回 true。 [ $a = $b ] 返回 false。
!= 检测两个字符串是否相等,不相等返回 true。 [ $a != $b ] 返回 true。
-z 检测字符串长度是否为0,为0返回 true。 [ -z $a ] 返回 false。
-n 检测字符串长度是否为0,不为0返回 true。 [ -z $a ] 返回 true。
str 检测字符串是否为空,不为空返回 true。 [ $a ] 返回 true。
文件测试运算符
操作符 说明 举例
-b file 检测文件是否是块设备文件,如果是,则返回 true。 [ -b $|file ] 返回 false。
-c file 检测文件是否是字符设备文件,如果是,则返回 true。 [ -b $|file ] 返回 false。
-d file 检测文件是否是目录,如果是,则返回 true。 [ -d $|file ] 返回 false。
-f file 检测文件是否是普通文件(既不是目录,也不是设备文件),如果是,则返回 true。 [ -f $|file ] 返回 true。
-g file 检测文件是否设置了 SGID 位,如果是,则返回 true。 [ -g $|file ] 返回 false。
-k file 检测文件是否设置了粘着位(Sticky Bit),如果是,则返回 true。 [ -k $|file ] 返回 false。
-p file 检测文件是否是具名管道,如果是,则返回 true。 [ -p $|file ] 返回 false。
-u file 检测文件是否设置了 SUID 位,如果是,则返回 true。 [ -u $|file ] 返回 false。
-r file 检测文件是否可读,如果是,则返回 true。 [ -r $|file ] 返回 true。
-w file 检测文件是否可写,如果是,则返回 true。 [ -w $|file ] 返回 true。
-x file 检测文件是否可执行,如果是,则返回 true。 [ -x $|file ] 返回 true。
-s file 检测文件是否为空(文件大小是否大于0),不为空返回 true。 [ -s $|file ] 返回 true。
-e file 检测文件(包括目录)是否存在,如果是,则返回 true。 [ -e $|file ] 返回 true。

字符串

字符串拼接

1
2
3
4
5
#!/bin/sh
your_name="qinjx"
greeting="hello, "$your_name" !"
greeting_1="hello, ${your_name} !"
echo $greeting $greeting_1

获取字符串的长度

1
2
string="abcd"
echo ${#string} #输出 4

提前字符串

1
2
string="alibaba is a great company"
echo ${string:1:4} #输出liba

查找子字符串

1
2
string="alibaba is a great company"
echo `expr index "$string" s`

查找字符串中的s的位置(位置是10),这个在linux下可以,mac下不可以

数组

bash只支持一维数组,获取数组元素用下标,从0开始,可以是整数或者算术表达式

定义数组

用括号表示数组,用空格分割

array_name=(value0 value1 value2 value3)

或者

array_name=(
value0
value1
value2
value3
)

也可以单独定义

array_name[0]=value0
array_name[1]=value1
array_name[2]=value2
读取数组
${array_name[index]}

eg:

valuen=${array_name[2]}

使用@ 或 * 可以获取数组中的所有元素,例如:

${array_name[*]}
${array_name[@]}

eg

1
2
3
4
5
6
7
8
#!/bin/sh
NAME[0]="Zara"
NAME[1]="Qadir"
NAME[2]="Mahnaz"
NAME[3]="Ayan"
NAME[4]="Daisy"
echo "First Method: ${NAME[*]}"
echo "Second Method: ${NAME[@]}"
数组长度的获取
1
2
3
4
5
6
# 取得数组元素的个数
length=${#array_name[@]}
# 或者
length=${#array_name[*]}
# 取得数组单个元素的长度
lengthn=${#array_name[n]}

printf

echo是简单的输出,shell的一个内部指令(双引号可有可无,单引号用于原样输出,eg:`date`)
printf用于格式化输出.

printf 命令的语法:

printf  format-string  [arguments...]

format-string 为格式控制字符串,arguments 为参数列表。
注意事项

  • printf 命令不用加括号
  • format-string 可以没有引号,但最好加上,单引号双引号均可。
  • 参数多于格式控制符(%)时,format-string 可以重用,可以将所有参数都转换。
  • arguments 使用空格分隔,不用逗号。
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
# format-string为双引号
$ printf "%d %s\n" 1 "abc"
1 abc
# 单引号与双引号效果一样
$ printf '%d %s\n' 1 "abc"
1 abc
# 没有引号也可以输出
$ printf %s abcdef
abcdef
# 格式只指定了一个参数,但多出的参数仍然会按照该格式输出,format-string 被重用
$ printf %s abc def
abcdef
$ printf "%s\n" abc def
abc
def
$ printf "%s %s %s\n" a b c d e f g h i j
a b c
d e f
g h i
j
# 如果没有 arguments,那么 %s 用NULL代替,%d 用 0 代替
$ printf "%s and %d \n"
and 0
# 如果以 %d 的格式来显示字符串,那么会有警告,提示无效的数字,此时默认置为 0
$ printf "The first program always prints'%s,%d\n'" Hello Shell
-bash: printf: Shell: invalid number
The first program always prints 'Hello,0'
$

if

  • if … fi 语句;
  • if … else … fi 语句;
  • if … elif … else … fi 语句。

基本格式

if [ expression ]
then
   Statement(s) to be executed if expression is true
fi

case

case 值 in
模式1)
    command1
    command2
    command3
    ;;
模式2)
    command1
    command2
    command3
    ;;
*)
    command1
    command2
    command3
    ;;
esac

case工作方式如上所示。取值后面必须为关键字 in,每一模式必须以右括号结束。取值可以为变量或常数。匹配发现取值符合某一模式后,其间所有命令开始执行直至 ;;。;; 与其他语言中的 break 类似,意思是跳到整个 case 语句的最后。

eg:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
echo 'Input a number between 1 to 4'
echo 'Your number is:\c'
read aNum
case $aNum in
1) echo 'You select 1'
;;
2) echo 'You select 2'
;;
3) echo 'You select 3'
;;
4) echo 'You select 4'
;;
*) echo 'You do not select a number between 1 to 4'
;;
esac

再来一个例子,很多时候我们在bash中会带参数使用(eg:cocos run -p android -m release)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#!/bin/bash
option="${1}"
case ${option} in
-f) FILE="${2}"
echo "File name is $FILE"
;;
-d) DIR="${2}"
echo "Dir name is $DIR"
;;
*)
echo "`basename ${0}`:usage: [-f file] | [-d directory]"
exit 1 # Command to come out of the program with status 1
;;
esac

运行时候:

$./test.sh
test.sh: usage: [ -f filename ] | [ -d directory ]
$ ./test.sh -f index.htm
File name is index.htm
$ ./test.sh -d unix
Dir name is unix

for循环

格式:

for 变量 in 列表
do
    command1
    command2
    ...
    commandN
done

列表是一组值(1.数字 2.字符串 每个值通过空格分割 )

eg1:

1
2
3
4
for loop in 1 2 3 4 5
do
echo "The value is: $loop"
done

eg2:

1
2
3
4
for str in 'This is a string'
do
echo $str
done

eg3:

1
2
3
4
5
#!/bin/bash
for FILE in $HOME/.bash*
do
echo $FILE
done

输出结果:

/Users/killer/.bash_history
/Users/killer/.bash_profile
/Users/killer/.bash_sessions
/Users/killer/.bashrc

解释:$HOME 当前登录用户的宿主目录,如果是root用户就是/root, 如果是user就是/home/登录名

while

语法:

1
2
3
4
while command
do
Statement(s) to be executed if command is true
done

eg

1
2
3
4
5
6
COUNTER=0
while [ $COUNTER -lt 5 ]
do
COUNTER=`expr $COUNTER + 1`
echo $COUNTER
done

util

语法:

1
2
3
4
until command
do
Statement(s) to be executed until command is true
done

command 一般为条件表达式,如果返回值为 false,则继续执行循环体内的语句,否则跳出循环。

break和continue

break:

在嵌套循环中,break 命令后面还可以跟一个整数,表示跳出第几层循环。例如:

break n

eg1:

1
2
3
4
5
6
7
8
9
10
11
12
13
#!/bin/bash
while :
do
echo -n "Input a number between 1 to 5: "
read aNum
case $aNum in
1|2|3|4|5) echo "Your number is $aNum!"
;;
*) echo "You do not select a number between 1 to 5, game is over!"
break
;;
esac
done

eg2:

1
2
3
4
5
6
7
8
9
10
11
12
13
#!/bin/bash
for var1 in 1 2 3
do
for var2 in 0 5
do
if [ $var1 -eq 2 -a $var2 -eq 0 ]
then
break 2
else
echo "$var1 $var2"
fi
done
done
continue

continue命令后面也可以跟一个整数,表示跳出几层
eg:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#!/bin/bash
for var1 in 1 2 3 4
do
for var2 in 5 6 7 8
do
if [ `expr ${var1} % 2` -eq 0 ]
then
echo "this number is ${var1} ${var2}"
if [ ${var2} -gt 5 ]
then
continue 2
else
continue 1
fi
fi
done
done

输出结果:

this number is 2 5
this number is 2 6
this number is 4 5
this number is 4 6

解释:当var1为偶数的时候,如果第二个参数大于5(就是6),真心continue 2,直接跳出两重循环

函数

shell的函数必须先定义后使用

Shell 函数的定义格式如下:

function_name () {
    list of commands
    [ return value ]
}

如果你愿意,也可以在函数名前加上关键字 function(我在mac和linux下测试无效):

function function_name () {
    list of commands
    [ return value ]
}

函数返回值,可以显式增加return语句;如果不加,会将最后一条命令运行结果作为返回值。

Shell 函数返回值只能是整数,一般用来表示函数执行成功与否,0表示成功,其他值表示失败。如果 return 其他数据,比如一个字符串,往往会得到错误提示:“numeric argument required”。

如果一定要让函数返回字符串,那么可以先定义一个变量,用来接收函数的计算结果,脚本在需要的时候访问这个变量来获得函数返回值。

函数调用的时候不用加括号

eg:带return的用法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#!/bin/bash
funWithReturn(){
echo "The function is to get the sum of two numbers..."
echo -n "Input first number: "
read aNum
echo -n "Input another number: "
read anotherNum
echo "The two numbers are $aNum and $anotherNum !"
return $(($aNum+$anotherNum))
}
funWithReturn
# Capture value returnd by last command
ret=$?
echo "The sum of two numbers is $ret !"

注意:函数的返回值在调用该函数后通过$?来获取
$(($a1+$a2))如果不这样写,会被当成字符串,也可以这样写:expr $a1 + $a2

像删除变量一样,函数也可以删除,unset 带上.f选项(-f好像也可以)

$unset .f function_name

shell函数的参数:
注意,$10 不能获取第十个参数,获取第十个参数需要${10}。当n>=10时,需要使用${n}来获取参数。

输出重定向

基本语法:

$ command > file

如果不希望文件内容被覆盖,可以使用 >> 追加到文件末尾,例如:

输入重定向:

command < file

文件包含

a.sh:

1
url="http://3toptop.top"

b.sh:

1
2
3
#!/bin/bash
. ./a.sh
echo $url

执行:

1
2
chmod +x b.sh
./b.sh

shell通配符(wildcard )

运算符 含义 实例
* 匹配 0 或多个字符 [ $a -eq $b ] a*b a与b之间可以有任意长度的任意字符, 也可以一个也没有, 如aabcb, axyzb, a012b, ab。
? 匹配任意一个字符 [ $a -ne $b ] a?b a与b之间必须也只能有一个字符, 可以是任意字符, 如aab, abb, acb, a0b。。
[list] 匹配 list 中的任意单一字符 [ $a -gt $b ] a[xyz]b a与b之间必须也只能有一个字符, 但只能是 x 或 y 或 z, 如: axb, ayb, azb。
[!list] 匹配 除list 中的任意单一字符 [ $a -lt $b ] a[!0-9]b a与b之间必须也只能有一个字符, 但不能是阿拉伯数字, 如axb, aab, a-b。
[c1-c2] 匹配 c1-c2 中的任意单一字符 如:[0-9] [a-z] [ $a -ge $b ] a[0-9]b 0与9之间必须也只能有一个字符 如a0b, a1b… a9b。
{string1,string2,…} 匹配 sring1 或 string2 (或更多)其一字符串 a{abc,xyz,123}b a与b之间只能是abc或xyz或123这三个字符串之一。

参考文档:C语言中文网