Shell 学习笔记

显示变量

两种方式显示变量:

  • echo $PATH
  • echo $

变量的设置原则:

  • 双引号内的特殊字符如$等,则可以保有原本的特性;
  • 单引号内的特殊字符仅为一般字符(纯文本);
  • 在一串命令中,还需要通过其他命令提供信息,可以使用反单引号`命令`或$(命令)
  • 使用export来使变量变成环境变量
  • 取消变量的方法为使用unset 变量名

环境变量

目前shell环境中,有多少默认的环境变量呢?可以利用两个命令来查询:

  • env
  • export,除此以外,还有一些额外的功能

PATH就是执行文件查找的路径,目录和目录之间以冒号隔开,目录的顺序也是重要的。

用set查看所有变量(含环境变量和自定义变量)

  • ?也是一个特殊的变量,关于上个执行命令的回传码;

export,自定义变量转成环境变量

环境变量和自定义变量有什么差异呢?
差异在于该变量是否被子进程继续引用。

那如何将环境变量转为自定义变量呢?使用declare

影响显示结果的语系变量(locale)

Linux到底支持多少的语系?可以使用locale -a查看

如果设置了LANG或者LC_ALL时,则其他语系变量就会被这两个变量所替代。

变量键盘读取、数组与声明:read array declare

read

read -[pt] variable

-p: 后面接提示符
-t: 后面接等待的秒数

read -p "Please input your name: " -t 10 name

declare

声明变量的类型。如果使用declare没有接任何参数,那么bash就会将所有的变量名称调出来,和set一样。delcare语法如下:

declare [-aixr] variable
-a: 将后面的变量定义为数组类型;
-i:将后面的变量定义为整数数字类型;
-x:用法与exprot用法一样,将后面的变量变成环境变量;
-r:将变量设置为只读类型,该变量不可被更改内容,也不可重设;

将环境变量变为自定义变量:

declare +x variable

单独列出变量的类型:

declare -p variable

数组

数组的设置方式:var[index]=content

数组的变量类型有趣的在于“读取”,一般来说,建议直接以${数组}的方式来读取。

通配符与特殊符号

符号 意义
* 代表0个到无穷多个任意字符
代表一定有一个任意字符
[] 同样是代表一定有一个在中括号内的字符(非任意字符)。例如,[abcd]代表一定有一个字符,可能是这四个中的一个
[-] 若减号在括号内,代表在编码顺序内的所有字符。例如,[0-9]代表0-9之间所有数字,因为数字的语系编码是连续的
[^] 若中括号第一个字符是^,表示原向选择,例如[^abc]代表一定有一个字符,只要是非a,b,c的其他字符就接收

示例:

  • 找出/etc/目录下文件夹名字刚好有5个字母的文件名:ll -d /etc/?????
  • 找出/etc/下面文件名含有数字的的文件名:ll -d /etc/*[0-9]*
  • 找出/etc/下面文件名开头非小写字母的文件名:ll -d /etc/[^a-z]*
  • 将上面例子找到的文件复制到/tmp中:`

bash中的特殊符号

符号 内容
# 注释符号
|转义符号,将特殊字符或通配符还原成一般字符
竖线 管道
; 连续命令执行分隔符,连续命令的界定
~ 用户的主文件夹
$ 使用变量的前导符
& 作业控制,将命令变成背景下工作
! 逻辑运算意义上的“非”
>,>> 数据流重定向,输出导向,分别是“替换”与“累加”
<,<< 数据流重定向,输入导向
'' 但因哈,不具有变量置换的功能
"" 具有变量置换的功能
`` 两个“`”中间为可以先执行的命令,也可以使用$()
() 中间为子shell的起始与结束
{} 中间为命令块的组合

数据流重定向

  • 标准输入:代码为0,使用<或<<
  • 标准输出:代码为1,使用>或>>
  • 标准错误输出:代码为2,使用2>或2>>

示例

  • 将stdout和stderr分别存到不同的文件中:find /home -name .bashrc > list_right 2> list_error
  • 将正确数据与错误数据通通写入同一个文件:find /home -name .bashrc > list 2>&1

命令执行的依据:;,&&,||

cmd

  • 在某些时候,希望可以一次执行多个命令。例如关机之前先执行两次sync同步写入磁盘后才shutdown计算机:
>:sync;sync;shutdown -h now
  • 在命令与命令中间利用;分号隔开,这样,分号之前的命令执行完成之后,就会接着执行后面的命令。万一两个命令彼此之间是有相关性的,前一个命令是否成功执行与后一个命令是否要执行有关,那就得动用&&||

$?命令回传码与&&||

两个命令之间有相依性,而这个相依性主要判断的地方就在于前一个命令执行的结果是否正确。

若前一个命令执行的结果为正确,在Linux下面会回传一个$?=0的值。那么怎么通过这个回传码来判断后续的命令是否要执行呢?这就得要“&&”及“||”帮忙。

命令执行情况 说明
cmd1&&cmd2 若cmd1执行完毕且正确执行(\(?=0),则开始执行cmd2;若cmd1执行结果为错误(\)?不等于0),则cmd2不执行
cmd1&#124&#124cmd2 若cmd1执行正确,则cmd2不执行;若cmd1执行结果错误,则执行cmd2

上述的cmdcmd2都是命令。

示例:

  • 使用ls查阅目录/tmp/abc是否存在,若存在,则用touch创建/tmp/abc/hehe
>ls /tmp/abc && touch /tmp/abc/hehe
  • 如果不存在/tmp/abc,则创建该目录
>ls /tmp/abc || mkdir /tmp/abc

命令是一个接着一个去执行的,因此,如果真要使用判断,那么这个&&||的顺序就不能搞错,一般来说,假设判断式有三个,就是:

cmd1 && cmd2 || cmd3

cmd如果成功,那么执行cmd2,否则就会执行cmd3。如果&&||的顺序颠倒,那么cmd1失败时,cmd3就会执行,cmd3执行成功就会执行cmd2,也就是会出现cmd2cmd3同时执行的情况!

shell运算符

表达式和运算符之间要有空格,例如 2+2 是不对的,必须写成 2 + 2

算术运算符

运算符 说明 举例
+ 加法 expr $a + $b 结果为 30。
- 减法 expr $a - $b 结果为 10。
* 乘法 expr $a \* $b 结果为 200。
/ 除法 expr $b / $a 结果为 2。
% 取余 expr $b % $a 结果为 0。
= 赋值 a=$b 将把变量 b 的值赋给 a。
== 相等。用于比较两个数字,相同则返回 true。 [ $a == $b ] 返回 false。
!= 不相等。用于比较两个数字,不相同则返回 true。 [ $a != $b ] 返回 true。

注意: - 乘号(*)前边必须加反斜杠()才能实现乘法运算; - if...then...fi 是条件语句,后续将会讲解

我们在 script 中经常有加1操作,以下四法皆可:

m=$[ m + 1]
m=`expr $m + 1` #这三者之间要有空白
m=$(($m + 1))
let m=m+1

参考

关系运算符

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

运算符 说明 举例
-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。

字符串运算符

a="abc"
b="efg"
运算符 说明 举例
= 检测两个字符串是否相等,相等返回 true。 [ $a = $b ] 返回 false。
!= 检测两个字符串是否相等,不相等返回 true。 [ $a != $b ] 返回 true。
-z 检测字符串长度是否为0,为0返回 true。 [ -z $a ] 返回 false。
-n 检测字符串长度是否为0,不为0返回 true。 [ -n $a ] 返回 true。
str 检测字符串是否为空,不为空返回 true。 [ $a ] 返回 true。

1、判断字符串为空

if [ -z "$str" ]; then
    echo "empty string"
fi

判断字符串为空的方法有三种:

if [ x"$str" = x ]
if [ "$str" =  "" ] 
if [ -z "$str" ] #(-n 为非空)

注意:都要代双引号,否则有些命令会报错,养成好习惯吧!

if [ "x${value}" != "x" ]  #不为空

参考:

2、判断文件是否存在

if [ -f /home/builder/.profile ]; then
    echo "File exists;"
fi

文件测试运算符

操作符 说明 举例
-b file 检测文件是否是块设备文件,如果是,则返回 true。 [ -b $file ] 返回 false。
-c file 检测文件是否是字符设备文件,如果是,则返回 true。 [ -c $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。

文件属性:
Linux 文件基本属性

shell注释

sh里没有多行注释,只能每一行加一个#号。

如果开发过程中遇到打断的代码需要临时注释起来,过一会儿取消注释,可以把这一段要注释的代码用

Shell字符串

字符串可以用单引号,也可以用双引号,也可以不用引号

单引号

str='this is $a'

即使存在变量a,这句话执行结果也还是this is $a。单引号里的任何字符都会原样输出,单引号字符串中的变量是无效的。

双引号

str="this is $a"

如果a变量值是world,那么这句话执行结果将是this is world。双引号的优点是可以有变量,双引号里可以出现准义字符。

拼接字符串

your_name="michael"
greeting="hello,$your_name"
greeting_1="hello,${your_name}"
echo $greeting $greting_1

获取字符串长度

string="abcd"
echo ${#string}

提取子字符串

string="dut is a great university"
echo ${string:0:5} #输出dut i

从index 0开始,提取5个字符

查找字符串位置(从0开始)

string="dut is a great university"
echo `expr index "$string is`

参考

shell数组

bash支持一维数组(不支持多维数组)。数组元素的下标由0开始编号。获取数组中的元素要利用下标,下标可以是整数或者算术表达式。

定义数组

shell中,用括号来表示数组,数组元素用“空格”符号隔开。

array_name=(v1 v2 …… vn)

还可以单独定义数组的各个分量:

array_name[0]=v1
array_name[1]=v2

可以不适用连续的下标,而且下标的范围没有限制。

读取数组

读取数组的格式一般是:

${array_name[index]}

使用@*可以获取数组中所有的元素(和python有区别,python中,$array_name即可显示全部):

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

获取数组的长度

获取字符串长度的方法与获取字符串长度的方法相同:

# 获取数组元素个数
length=${#array_name[@]}
# 或者
length=${#array_name[*]}
# 取得数组单个元素的长度
lengthn=${#array_name[n]}

Shell echo命令

显示转义字符

echo "\"It is a test\""

结果显示:

"It is a test"

显示变量

如果变量与其他字符相连,需要使用大括号:

mouth=8
echo "${mouth}-1-2009"

双引号可以省略。
结果是:

8-1-2009

显示结果重定向至文件

echo "It is a test">myfile

原样输出字符串

若需要原样输出字符串(不进行转义),请使用单引号。例如:

echo '$name\"'

输出:

$name\"

显示命令执行结果

使用`符号包含命令

echo `date`

从上面可看出,双引号可有可无,单引号主要用在原样输出中。

Shell printf命令,格式化输出语句

printf命令用于格式化输出,是echo命令的增强版。printf不像echo那样会自动换行,必须显示添加换行符(\n)。

printf命令语法:

printf format-string [arguments...]

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

shell if else语句

shell语句有三种if...else语句:

  1. if...fi;
  2. if...else...fi;
  3. if...elif...else...fi;

1.if...else

if [ expression ]
then
  Statements to be executed if expression is true
fi

最后必须以fi来结尾闭合,fi就是以if倒过来萍姐,后面也会遇见。

注意:expression和方括号之间必须有空格,否则会有语法错误。

2.if...else...fi

#!/bin/sh
a=10
b=20
if [ $a -eq $b ]
then
echo "a is equal to b"
else
echo "a is not equal to b"
fi

3.if...elif...fi

语法为:

if [ expression 1 ]
then
Statement(s) to be executed if expression 1 is true
elif [ expression 2 ]
then
Statement(s) to be executed if expression 2 is true
elif [ expression 3 ]
then
Statement(s) to be executed if expression 3 is true
else
Statement(s) to be executed if no expression is true
fi

可以留意到,有if后,会有thenelse后面则没有接then

if...else语句也可以写成一行,以命令的方式来运行:

if test $[2*3] -eq $[1+5];then echo 'two number are equal';fi;

输出:

two number are equal

test命令用于检查某个条件是否成立,与方括号[]类似

Shell for循环

for循环一般格式为:

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

列表是一组值(数字、字符串等)组成的序列,每个值通过空格分隔。

in列是可选的,如果不用它,for循环使用命令行的位置参数。

eg1:

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

显示主目录以.bash开头的文件:

#!/bin/bash
for FILE in $HOME/.bash*
do
echo $FILE
done

结果:

/root/.bash_history
/root/.bashrc

Shell case esac语句

case语句匹配一个值或一个模式,如果匹配成功,执行相匹配的命令。

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

取值后面必须为关键字in,每一模式必须以右括号结束。取值可以为变量或常数。;;与其他语言中的break类似,意思是跳到整个case语句的最后。

如果无一匹配模式,使用星号*捕获该值,再执行后面的命令。

Shell while循环

while command
do 
  Statement(s) to be executed if command is true
done

命令执行完毕,控制返回循环顶部,从头开始直至测试条件为假。

declare -i COUNTER
COUNTER=0
while [ $COUNTER -lt 5 ]
do 
  COUNTER=`expr $COUNTER + 1` #注意,这里有空格 d等价 $(($COUNTER+1)
  echo "$COUNTER"
done

Shell until循环

until循环执行一系列命令直到条件为True时。util循环与whild循环在处理方式上刚好相反。一般while循环优于until循环,但在某些是偶,也只是极少数情况下,uunti循环更加有用。

until command
do
  Statement(s) to be executed until commands is true
done

command一般为条件表达式,如果返回False,则继续执行循环内语句。

a=0
until [ ! $a -lt 10 ] #注意,感叹号之后,也要有空格
do 
  echo $a
  a=`expr $a + 1`
done

输出:

0
1
2
3
4
5
6
7
8
9

Shell break和continue

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!"
    break
    echo "Game is over!"
  ;;
esac
done

Shell函数:Shell函数返回值、删除函数、在终端调用函数

Shell函数定义的格式如下:

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

如果愿意,也可以在函数名前面加上关键字function

函数返回值,如果不加,会将最后一条命令运行结果作为返回值

调用函数只需要给出函数名,不需要加括号。

funWithReturn(){
  echo "The function is to get sum of two numbers"
  echo -n "Input first number:"
  read aNum
  echo -n "Input another number:"
  read anotherNum
  echo "Two number are $aNum and $anotherNum !"
  #return $(($aNum+$anotherNum))
  return `expr $aNum + $anotherNum` #等价于 $(($aNum+$anotherNum))
}
funWithReturn

ret=$?
echo "The sum of two number is $ret !"

函数返回值在调用该函数后通过$?来获得。

像删除变量一样,删除函数也可以通过unset命令,不过要加上.f选项。

unset .f function_name

如果希望直接从终端调用函数,可以将函数定义在主目录下的.profile文件,这样每次登录后,在命令提示符后面输入函数名字就可以立即调用。

函数参数

调用函数可以向其传递参数。在函数体内部,通过$n的形式来获取参数的值,例如,$1表示第一个参数。

示例:

#!/bin/bash
funWithParam(){
  echo "The value of the first parameter is $1 !"
  echo "The value of the second parameter is $2 !"
  echo "The value of the tenth parameter is $10 !"
  echo "The value of the tenth parameter is ${10} !"
  echo "The value of the eleventh parameter is ${11} !"
  echo "The amount of the parameters is $# !" # 参数个数
  echo "The string of the parameters is $* !" # 传递给函数的所有参数
}
funWithParam 1 2 3 4 5 6 7 8 9 34 73

结果:

The value of the first parameter is 1 !
The value of the second parameter is 2 !
The value of the tenth parameter is 10 ! #$10 不能获取第十个参数,获取第十个参数需要 ${10}
The value of the tenth parameter is 34 !
The value of the eleventh parameter is 73 !
The amount of the parameters is 11 !
The string of the parameters is 1 2 3 4 5 6 7 8 9 34 73 !

注意:当 n>=10 时,需要使用${n}来获取参数。

特殊变量来处理参数:

特殊变量 说明
$# 传递给函数的参数个数
$* 显示所有传递给函数的参数
$@ \(*相同,但是略有区别。\)*是将参数作为一个整体输出,$@将各个参数分开输出
$? 函数的返回值
$0 Shell本身的文件名

Shell输入输出重定向

输出重定向

命令的输入不仅可以是显示器,还可以很容易转移到文件,这被称为输出重定向。

command > file

示例:

who > users

执行的时候,不会在显示器看到任何输出。

输出重定向会覆盖内容:

echo line1 > users

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

输入重定向

计算 users 文件中的行数:

[root@HGH1000059721 tmp]# wc -l users
1 users
[root@HGH1000059721 tmp]# wc -l < users
1

注意:上面两个例子的结果不同:第一个例子,会输出文件名;第二个不会,因为它仅仅知道从标准输入读取内

重定向深入讲解

一般情况下,每个 Unix/Linux 命令运行时都会打开三个文件:

  • 标准输入文件(stdin):stdin 的文件描述符为0,Unix 程序默认从 stdin 读取数据。
  • 标准输出文件(stdout):stdout 的文件描述符为1,Unix 程序默认向 stdout 输出数据。
  • 标准错误文件(stderr):stderr 的文件描述符为2,Unix 程序会向 stderr 流中写入错误信息。

默认情况下,command > file 将 stdout 重定向到 file,command < file 将 stdin 重定向到 file

如果希望stderr重定向到file,可以这样写:

command 2>file  #注意,2和>之间没有空格

如果希望 stderr 追加到 file 文件末尾:

command 2>>file

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

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

command > file 2>&1

或:

command >> file 2>&1

/dev/null

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

commadn >/dev/null

/dev/null是一个特殊的文件,写入到它的内容都会被丢弃;如果尝试从该文件读取内容,那么什么也读不到。

如果希望屏蔽stdoutstderr,可以这样写:

command &>/dev/null

Shell文件包含

Shell可以包含外部脚本,将外部脚本的内容合并到当前脚本。

Shell中包含脚本可以使用:

. filename

source filename

两种方式效果相同,一般使用点号(.),但是注意点号和文件名之间有一空格

示例:
创建两个脚本,一个是被调用脚本subscript.sh,内容如下:

url="www.baidu.com"

一个是主文件main.sh

#!/bin/bash
. ./subscript.sh
echo $url

执行脚本:

>chmod +x main.sh
>./main.sh
www.baidu.com

FAQ

交互式SHELL和非交互式SHELL、登录SHELL和非登录SHELL的区别

参考

posted @ 2019-03-03 16:05  Michael翔  阅读(290)  评论(0编辑  收藏  举报