Shell:Shell十三问重读笔记

2018年05月23日 1994Browse 4Like 0Comments

01、为什么叫Shell?

Command Interpreter,交互的命令解释器,它是相对于kernel的说法,是kernel的外壳程序,用来实现用户与kernel的命令行沟通:将用户的命令翻译给内核处理,同时将内核的处理结果反馈给用户。

  • 系统登陆时取得的shell称之为login shell或primary shell。
  • 在shell中所下达的命令,都是shell所fork出产生的子进程。
  • 在shell中执行的脚本,则是另外由一个单独的非交互的sub-shell取执行,然后在由sub shell执行script中的各命令(也会产生相应的进程)

Linux OS支持多种shell,具体在/etc/shells中可以看到,主要分为sh(如sh、bash)和csh(csh、tcsh、ksh)两大类。

02、Shell prompt(PS1)与Carrige Return(CR)的关系

PS1是用来告诉用户可以输入了,通常有两种形式:

  • $:给普通用户使用
  • #:超级用户使用

CR由Enter回车键产生,是让用户告诉shell,我的命令输入完成,shell可以去解释和执行了。

命令行的定义:位于shell prompt和CR字符之间用户输入的文字。

一个标准的命令行由三部分组成,其中后面两个部分是可选的。

command-name options arguments

重要原理

Shell会根据IFS(Internal Field Separator)将命令行拆解为字段(word),然后再处理各字段中的特殊字符(meta),最后再重组命令行。

常见的IFS有:空格键、制表符、回车键等。

命令的名字可以来源于:

  • Shell内建命令
  • $PATH之下的外部命令
  • 命令别名
  • 指定路径的其他外部命令或脚本

03、echo命令

echo的作用:将其argument送至标准输出。默认在显示完输入参数之后,再补充送出一个换行符(new-line character)。如要取消该换行符,采用echo -n arguments即可。
所以,直接输入一个不带任何选项和参数的echo命令,将输出一个空白行(就是new-line character)。

$echo

$

而输入echo -n相当于完成echo输入,再回车到另一行,echo执行将无任何输出。

$echo -n
$

echo命令的三个选项:

  • -e:启用对反斜杠转义字符的识别
  • -E:关闭反斜杠转义字符(默认为关闭)
  • -n:取消行尾的换行符

所以echo常用来检查变量值

04、引用和meta处理(单引号、双引号、反斜杠)

4.1 Shell中的字符类型

命令行中的每一个字符,要么是literal character,要么是meta character。前者就是普通字符,后面是具有特别功能和意义的特殊字符(保留字符)。例如,IFS和CR都是meta。其他常用的meta还有:

  • =:设置变量
  • $:用来对变量或运算进行替换
  • >:重定向stdout
  • <:重定向stdin
  • |:管道
  • &:重定向文件描述符,或将命令置于后台运行
  • ():将内部的命令置于nested-subshell中运行,或用于整数运算、命令替换。
  • {}:将内部的命令置于non-named function中运行,或用于变量替换的边界范围界定。
  • ;:执行前一个命令结束后,不判断返回值,继续执行分号后的下一个命令。
  • &&:判断前一个命令返回值是否为true,如实则继续执行下一命令。
  • ||:判断前一个命令返回值是否为false,如实则继续执行下一命令。
  • !:执行history中的命令。
  • 其他……

这些特殊字符由于具有特别的含义和功能,如不该出现的时候却使用了会导致命令出错或者达不到预期的效果。因此,在大部分情况下,我们根据需要会选择性的通过quoting来关闭这些meta。

  • escape:逃脱符。仅关闭其后紧接的单一meta。
  • soft quote:双引号。部分关闭内部的meta,如$、``和\这三种则不被关闭。
  • hard quote:单引号。全部关闭内部的meta字符。

空格键在单引号和双引号内均会被关闭
单引号、双引号在soft quote和hard quote中均被关闭
ENTER键在这三种引用内均被关闭,视为换行符,但是在命令重组阶段分别被视作换行符、空格符或无字符。

4.2 区分shell meta和command meta

$awk {print $0} 1.txt       #语法错误:{}在命令行中被视作命令块用,而括号中的命令又没加上;分号
$awk '{print $0}' 1.txt     #OK:用''关闭所有大括号meta,使其成为awk的参数,避免在shell中处理
$awk "{print \$0}" 1.txt    #OK:由于$在""未被关闭,所以需加用\来关闭
$awk \{print\ \$0\} 1.txt   #OK:没有使用''和"",全部使用\来关闭shell meta
$awk "{print $0}" 1.txt     #语法不报错:""不能关闭$,所以$0还是shell变量,$0不能成为awk的Field Number。因为此处不是在执行脚本,所以$0也没有意义,被当作0处理,因此逐行打印0。
$awk "{print $1}" 1.txt     #语法不报错:""不能关闭$,所以$0还是shell变量,$0不能成为awk的Field Number。因为此处不是在执行的脚本内部,所以$1也没有意义,被视为空值。
$awk "{print $10}" 1.txt     #语法不报错:""不能关闭$,所以$0还是shell变量,$0不能成为awk的Field Number。因为此处不是在执行的脚本内部,所以$1也没有意义,被视为空值。因为位置参数最大是$9,又没有采用边界界定符,所有$10被视作$1和0,而$1被无视,所以等价于逐行打印0。
  • 上述第一行,由于{}在shell中是用来将其中的命令组合起来成为一个命令块,那么{print $0}被视作为一命令块,其语法要求最后一个命令后面要用分号。所以上述写法会报语法错误!
  • 上述第二行,用hard quote关闭了{}的命令块功能,同时也关闭了空格和\$0在shell中的meta作用,从而不被Shell作为Shell meta先解析,使得这几个meta成为命令awk自身的command meta($0是awk的field number,表示整行内容),而作为命令行参数的内容去解析。
  • 使用第三行和第四行也同样达到取消{}、空格和$作为shell command的效果。其他:
    • 发现在shell中任意命令(非执行script)末尾输入\$1-\$9,\$1-\$9会被无视、语法不报错。
    • 在shell中任意命令(非执行script)末尾输入\$0,结果有所不同、语法不报错。

假设awk使用的$x的值从shell中获取进来,可采用变量替换来做(不要直接用hard quote,因为hard quote关闭了所有shell meta,无法直接进行变量替换),采用下述方法:

$A=0
$awk "{print \$$A}" 1.txt
$awk \{print\ \$$A\} 1.txt
$awk '{print $'$A'}' 1.txt
$awk '{print $'"$A"'}' 1.txt

05、变量与export

5.1. 定义变量(set,=)

设定变量采用 变量名=变量值 的格式,注意:

  • =前后不能有IFS,=后面可以为null
  • 变量名是 Case Sensitive(大小写敏感的)
  • 变量名避免使用meta、$
  • 变量名第一个字符不能是数字
  • 变量名长度限制在256个字符
  • 首次set变量可以理解为定义变量并对其赋值
  • 之后每次对变量名执行=,相当于对变量重新赋值
  • 已经赋值的变量已存在,这对变量做进阶处理时${var}有影响

5.2. 替换变量(符号$)

Shell强大的原因就是因为在命令行中其支持替换(变量替换、命令替换),变量替换的格式:
$符号加上变量名,如$A,A必须已定义。
注意:

  • 变量替换是指用变量的值来替换$变量名,将其再放置到command line之中;
  • 不要用数学逻辑来套用变量设定,变量的值只看变量每次被set时=后面的值,与其他无动态关联关系,如:
A=B
B=C

这里,A的值不会变成C

A=B
B=$A
A=C

这里,A的值最终为C,B是一个变量,值为C,也不会让B的值等于C

利用命令行的变量替换和区隔符号:可以append变量的值:

A=B:C:D
A=$A:E

而不能采用:

A=BCD    #A的值D
A=$AE    #此处,AE被视作一个变量,而不是先$A,再扩充E。若AE未定义,那么$AE为空,使得A为空值。

而应采用:

A=BCD
A=${A}E   #使用{}限定变量名的范围,A变为BCDE

注:${}有很多用法

5.3. 设定环境变量(export)

环境变量即为全局起作用的变量,在shell上定义的变量均属于local variable,只有通过export输出之后才能成为环境变量,供后续其他命令使用。但是也仅仅是对当前Shell的session起作用。语法:

A=B       #先定义
export A  #变量A不需要带$,或者直接
export A=B

例如,下面使用了$A,会进行变量替换

A=B
B=C
export $A   #$A替换为B,那么等价于export B,是将变量B输出为环境变量

另外,直接使用export命令可以查看已经设定的环境变量

export

export环境变量的生存期:

  • 在shell中export的环境变量仅对当前shell的session起作用(写在内存中),shell退出后不再起作用
  • source(点命令)配置文件可以使得配置文件中的设定在当前shell中立即生效(当前不用重启)
  • 如要使得变量任何时候对各session均有效的环境变量,需将其写入/etc/profile(全部用户)或者.bashrc(当前用户)等配置文件中(source或重启之后永久生效)

5.4. 取消变量(unset)

取消变量等价于删除变量,该变量将未定义、不存在。但以下在shell中输出均一样。

$ A=
$ echo $A
$
$ unset A
$ echo $A  #
$

但是null value的变量和unset的变量在变量的进阶处理${var}中有很大的区别,例如

$ str=  #null value
$ var=${str=expr}  #变量str已set为null,str在此不再被set赋值,set不执行
$ echo $var
$
$ echo $str
$
$ unset str   #取消删除了str变量
$ var=${str=expr}      #变量str已先被unset,str在此相当于重新被set赋值为expr
$ echo $var
$ expr
$ echo $str
$ expr

unset 函数名,还可以用来取消在shell中定义的函数。

06、fork、exec、source

进程运行数据只能从父进程向子进程拷贝传递,但不能逆向传递,所以在子进程可以做的事情,回到父进程不一定可以做,也就是说子进程的环境变化不会影响原有父进程的环境。所以,在script(子进程运行)中可以正确做的事情,回到primary Shell中完全可能不能正常运行。

三种方式运行某个script的区别:

  • fork:产生一个子进程(sub-shell)来运行scrip,在sub-shell进程中执行脚本代码。
  • exec:直接运行script并从运行处接替和覆盖父进程代码,不再返回原来代码处。此exec与C函数exec()不同。
  • source:直接在当前的进程环境中运行,可避免父子进程中环境不一致问题。

注意: 无论采取哪种方式,在Shell中运行的命令都是主Shell的一个或多个child process。

  • 主shell产生(fork出)的sub-shell是一个child process,脚本中的命令在该sub-shell进程中运行;
  • source命令将脚本中的命令加载到主Shell所在的进程空间中,不需产生sub-shell运行脚本内容;
  • exec命令将脚本命令放到主Shell所在的进程环境中运行,而且脚本运行后将直接取代原进程环境。

07、()、{}和函数

本节关键词:群组化命令、命令组合、命令块、匿名命令组、函数

将多个命令集中处理,有这么几种方法:

  1. 使用():将组合的命令放到sub-shell中执行,也称nested sub-shell,再返回,类似于上节fork执行脚本。
  2. 使用{}:匿名的命令组,也称non-named command group,将组合的命令放在同一shell内完成,类似于上节中source 脚本名。{}内的最后一条命令需要用;分开;
  3. 定义函数并调用:同上,只是的具名了命令组。增加了代码的可复用性!

知识运用扩展:

我们可以将很多命令分别写成有用的函数,集中放置到一个脚本之中,然后使用source命令加载该脚本,那么Shell中就可以直接反复使用这些函数了。例如:/etc/rc.d/init.d/functions这个脚本文件中有大量的函数。

08、$()或``、$(())、${}

8.1 命令替换:$()或``

命令替换的过程:先执行$()或``内的命令,然后用其执行结果重组命令行。

$()和``:用来做命令替换,替换完/运行结果再做命令重组

  • $()的好处:多次替换或嵌套替换时不用\来做逃脱处理,而反引号嵌套时则容易出错,必须加\
  • ``的好处:可移植性更好

8.2 变量替换、字符串、数组操作:${}

${}在变量替换和赋值时的作用:

  • 精确界定变量范围,防止变量替换时出错(贪婪匹配替换)
  • 对变量扩展赋值:根据变量状态(三种状态unset、null value、non-null value及其组合状态)来对其条件性赋值:-(未设定)、:-(未设定或为空值)、+(已设定:空值或非空值)、:+、=、:=、?、:?

${}用于操作字符串和数组:

  • 计算变量值的字符长度:${#var}
  • 变量处理(字符串):
    • Trim:采用 #(去左边—掐头) 和 %(去右边—去尾)剔除一次。##和%%是最大匹配,完全剔除。
    • Replace:采用 /xx/yyy替换首次匹配的xx,或//xxx/yyy全部替换xxx为yyy。
    • Substring:采用:x:y来提取从x开始的y个字符的字串。
  • 操作数组,例子:
    $ A=(a b c def) #定义数组A
    $ ${A[@]}       #获取数组内容,得到 a b c def
    $ ${A}          #同上
    $ ${A[0]}       #取得数组第一个元素,为a
    $ ${#A[0]       #计算第一个元素的长度,为1
    $ ${#A}         #计算数组A的长度,为4$
    

8.3 整数计算:$(())

Shell不支持小数和浮点数运算,整数运算有这三种运算类型:

  • 四则运算:+、-、*、/
  • 余数运算:%
  • 逻辑运算:&、|、^、!分别代表与、或、异或、非运算

例子(变量名可以直接用或者用$符号来替换):

$ a=5; b=7; c=2
$ echo $((a+b*c))     #或 echo $(($a+$b*$c))
19
$ echo $(((a+b)/c))   #或 echo $((($a+$b)/$c))
6
$ echo $(((a*b)%c))   #或 echo $((($a*$b)%$c))
1

不同进制的运算方法(采用#,但运算结果的输出总是十进制):

$ echo $((16#2a))     #将2a以16进制运算,输出仍然为十进制
42
$ umask 022
$ echo "obase=8; $(( 8#666 & (8#777 ^ 8#$(umask)) ))"|bc  #使用bc程序计算并以八进制输出计算结果
644

另外,单纯使用(())可用于重定义变量值,或者做整数条件测试(只有在这种双括号的运算方式下,才可以用<,>,==,!=等直接判断,且判断符号可以不用加空格)。可对比[10.2]章节。

$ a=5; ((a++)); echo $a  #将a的值变为6了
6
$ a=5; ((a++)); echo $a  #将a的值变为4了
4
$ a=5; b=7; ((a<b))  #测试a是否小于b
$ a=5; b=7; if ((a<b)); then echo 0; else echo 1; fi
0
$ ((a==5)) && echo 'equal!'  #这里是用整数条件运算结果
equal!

09、$@、$*、$#、$$

Shell内置的positional parameter:位置参数变量

执行脚本时的语法格式:script_name parameter1 parameter2 …… parameterX

$0代表脚本名,$1-$9为脚本输入的第一至第九个参数值。其具体值是根据IFS的拆分结果而来的。
脚本内部的函数也支持位置参数:$0依然表示函数所在的脚本名(而非函数名),$1-$9表示调用函数时的输入位置参数。

其他:例如Shell命令行中的命令,获取的$0为-bash,是创建Primary Shell时传入的唯一参数。所以:

$ echo $0
-bash
$ ls $0    # 或 ls -bash 等部分命令均支持
$ find $0  # 传给find命令 则命令行有无
find: unknown predicate `-bash'

位置参数的语法陷阱

当位置值大于9的参数采用$10等2位数的写法,Shell在检查输入行时进行变量替换$10会替换成$1和0(这种截取与$AB变量名为字符串的不一样,$AB不会被截取,参谋上节)。如要获取位置大于9的参数,可以采取:

  • ${}将其限定变量名的范围,如${10}就不会被解析为$1和0;
  • 使用前先用shift命令移动参数列表:shift x表示将参数列表左移x个,那么前面x个参数将不在$1-$x之中,原来的第x+1个参数成为现在的$1,以此类推。

带刀符号组合的具体含义及用途:

  • $#:获得脚本传入参数的个数(不包含$0),如测试脚本是否输入参数:[ S# = 0 ]
  • $$:显示脚本进程PID
  • $@:脚本的全部输入参数串(不包含$0),获得由$#个被IFS拆分的独立字段组成的参数串。
  • $*:脚本的全部输入参数(不包含$0),同上。

说明:

  • 在不带quote的情况下,$@与$*效果一样,将所有位置参数以IFS(空格)分别列出,拆分结果可能多于实际参数个数。
  • 在带soft quote的情况下,"$*"变为将所有参数整体视作一个字段/单词,而"$@"则按实际参数拆分字段。
#!/bin/bash
index=1 
echo "Listing args with\"\$*\":"
for arg in "$*"
do
   echo "\$$index=$arg"  # 通过$arg来取得循环对象中的参数值
   let "index+=1"
done
echo "所有的参数被认为是一个单词"

echo
index=1
echo "Listing args with \"\$@\":"
for arg in "$@"
do
	echo "\$$index=$arg"
	let "index+=1"
done
echo "所有的参数被认为是各个独立的单词"
# 接上面代码
echo
index=1
echo "Listing args with \$* (未被引用):"
for arg in $*
do
	echo "\$$index=$arg"
	let "index+=1"
done
echo "所有的参数被认为是各个独立的单词"

exit 0

执行测试及结果:

# ./test 1 2 3 4
Listing args with"$*":
$1=1 2 3 4
所有的参数被认为是一个单词

Listing args with "$@":
$1=1
$2=2
$3=3
$4=4
所有的参数被认为是各个独立的单词

Listing args with $* (未被引用):
$1=1
$2=2
$3=3
$4=4
所有的参数被认为是各个独立的单词

也可用shift写入函数来测试:

#!/bin/bash
# 由于位置参数在shift后就参数列表中删除了,那么前面被shift过的参数将回不来了。
# 如果此时直接再想从头获取参数,就再也获取不到参数了。
# 要么使用getopt保存到数组之中去每次需要时再获取;
# 要么写成函数,当函数返回了位置参数又重新归位了。且可以被多次调用。
function show_args(){
        echo "number of words detected: $#"
        index=$#  #如直接将$#写入循环条件,而每次shift之后$#的值会改变,导致结果不对
        for ((i=1;i<=$index;i++))
        do      
            #echo "\$$i=$i" #由于$i替换之后会是数字1,2,3,而不是输出位置参数
            echo "\$$i=$1"
            shift   #默认为 shift 1
        done    
}
echo "Number of args input(Real):$#"
echo -ne "\nTest \$@: "; show_args $@
echo -ne "\nTest \"\$@\": "; show_args "$@"
echo -ne "\nTest \$*: "; show_args $*
echo -ne "\nTest \"\$*\": "; show_args "$*"

执行测试及结果:

# sh testargs.sh p1 "p2 p3" p3
Number of args input(Real):3

Test $@: number of words detected: 4
$1=p1
$2=p2
$3=p3
$4=p3

Test "$@": number of words detected: 3
$1=p1
$2=p2 p3
$3=p3

Test $*: number of words detected: 4
$1=p1
$2=p2
$3=p3
$4=p3

Test "$*": number of words detected: 1
$1=p1 p2 p3 p3

10、&& 与 ||

10.1 命令返回值$?

每一个命令运行结束时都会有一个返回值,返回值的范围取值是0-255之间,或者由程序或脚本自行定义。

  • 在script之中,用exit RetVal 来指定返回值,若没有指定,则结束时以最后一道命令的返回值作为Ret Value。
  • 在function之中,用return RetVal来指定返回值。

返回值的作用就是用来判断进程/命令的退出状态的,我们可以通过全局的变量$?来取得干结束程序的返回值。返回值($?)只有两种状态:0(true)或非0值(False,例如,当命令语法错误时返回的值通常是127,ls命令执行找不到文件时返回1,等等)。

&&和||是用来将多个命令组合的,同时根据前一个命令的执行返回值来决定后面的命令是否执行:

  • cmd1 && cmd2:是指当cmd1返回为true时,才执行cmd2,否则cmd2永远不执行。
  • cmd || cmd2:是指cmd1返回为false时,才执行cmd2,否则cmd2永远不执行。

10.2 条件测试命令

测试某一条件返回值的命令和语法:test expression或者用[ expression ]

两种格式均可。

使用方括号这种语法时,表达式的左右两边必须留空格(因为[ ]是Bash中的关键字单词,必须用元字符空格来分开,不带空格的话就成为不了单词)。

使用man test即可查看test命令的帮助信息。

test的测试对象只有三种,不同的测试对象表达式格式不一样:

  • string:字符串,例:[ "$A" = 123 ],等号前后也有空格(因为不是赋值)
  • integer:整数(不支持负数和浮点数)。例:[ "$A" -eq 123]
  • file:文件。例:[ -e "$A" ]

举例,测试字符串是否为空的方法:

$ [ -n "$string" ]  #string 是否非空,注意最好加上"",原因后面有说
$ test -n "$string"
$ [ x"$string" = x ] 
$ [ ${string} ]

采用[ ! expression ]在表达式前面加上感叹号,表示条件为假的测试。

条件测试也支持多重复合条件组合进行:

  • [ expr1 -a expr2 ]:与,两个表达式全部为真时返回0(true)
  • [ expr1 -o expr2 ]:或,两个表达式有一个为真时返回0(true)

例如,[ -d "$file" -a -x "$file" ]:当$file是一个目录同时具备x权限时,返回0。

注意:字符串测试[ string1 = string 2 ]表达式的语法陷阱:要求 = 的左右两边必须均有字符串,其中包括空字符串""或null(可用soft quote或hard quote取得)。即:[ = string]是错误的,但是[ null = string ][ "" = string ]是被允许的。例如下述情况就不可以:

$unset A 或者 set A= 或 set A=""
$[ $A = abc ]  #这是因为$A做了变量替换之后(会去掉引号),表达式变成了 [ = abc ]
-bash: [: =: unary operator expected

预防办法:

  • 遇到变量替换时,对变量加上soft quote双引号(双引号可以使得为空或为设置的变量取得空字符串值)
  • 采用变量扩展防止变量未定义或定义为空值:A=${A:-abc}

10.3 使用显式条件测试来使命令条件执行

$ A=123
$ [ -n "$A" ] && [ "$A" -lt 100 ] || echo 'too big!'  #echo语句之前若为false才执行echo
too big!
$ unset A
$ [ -n "$A" ] && [ "$A" -lt 100 ] || echo 'too big!'  #同上
too big!
$ ((A<100)) || echo 'too big!'  #这里是用整数条件运算结果
too big!
$ (($A<100)) || echo 'too big!'  #这里是用整数条件运算结果
too big!

上述两条命令中,第一条命令的第二个测试结果为false;第二条命令中的第一个测试结果为false,所以echo之前的这两个组合命令执行完为false,后面的echo当然会执行输出too big!

11、IO重定向(< 、 > 和 |)

11.1. 标准文件描述符(0/1/2)

  • 0:标准输入,通常是keyboard
  • 1:标准输出,通常是monitor或screen
  • 2:标准错误,通常是monitor或screen

11.2. stdout(1)和stderr(2)

程序默认的标准输入是键入的内容,而标准输出和错误信息都是送到monitor了。如何修改输入输出信道呢?

  • 通过<可以修改标准输入的数据信道:例如,0<file(<符号前后无空格)就是将标准输入设定为file文件,即将file指向fd 0。由于0<是默认值,所以采用<file等价于0<file。通常省略0。
  • 还可以通过<<使用HERE Document来完成输入(从而不用单独已经存在的文件了)。
$ cat << END #输入END标记开始,输入EOF或FINISH其他均可,只需要结束时输入通用的内容
first line input
second line input
...
last line input
END #输入END结束
  • 通过>可以修改标准输出和错误的输出数据信道。1>或2>就是用来修改标准输出和标准错误信息的输出信道的。同样,由于1是>的默认值,因此1>与>作用相同,指修改stdout。但是2>的2就不能省略。

11.3. >& 合并输出信道

$ ls my.file no.such.file >file.both 2>file.both

如果打算将标准输出和错误均同时送入至一个文件之中,上述指令就会导致文件中已写入的信息被后写入的信息覆盖。解决办法就是合并信道,两种合并信道的方法:

  • 2>&1就是将stderr(2)合并至stdout(1)之中输出
  • 1>&2>&2就是将stdout(1)合并至stderr(2)输出

所以,前面的问题就可以解决了:

$ ls my.file no.such.file >file.both 2>&1 #或
$ ls my.file no.such.file 2>file.both >&2

11.4. /dev/null、2>&1、>&2、>&或&>、>|

如果我不想看到任何的输出或错误信息,就可以这样子:

$ ls my.file no.such.file > /dev/null 2>&1 #或
$ ls my.file no.such.file 2> /dev/null >&2 #或
$ ls my.file no.such.file &> /dev/null #或
$ ls my.file no.such.file >& /dev/null

采用>每次写入文件的内容会被覆盖,换用>>是表示追加写入不会被覆盖,但再来一次>,文件又回到解放前了。

默认任何文件均可以被覆写,采用set -o noclobber可以限制文件不被覆写(overwrite),而set +o noclobber取消这个限制,文件可以被覆写。

既可防止文件被覆写,又能临时写入文件的方法:使用 >| 来写入(两个符号之间不能有空格)。

$ set -o noclobber
$ echo "new input text" >| file
$ cat file
new input text

理解重定向的优先级:

$ echo "some text in file" > file
$ cat file #或 cat <file
come text in file
$ cat <file>file
$ cat file #或 cat <file

为什么file文件的内容被清洗了呢?原因是IO重定向时,先优先处理stdout和stderr,然后再处理stdin,所以file被先清空了。那么下述两个命令的含义是什么呢?

其他问题:如何理解cat <>file和cat >file

$ cat <> file  #这个命令等价于 cat 0< file, cat 1>file,那么就是以读和写的方式打开file,当file存在时命令执行的表现等价于:cat < file,当file不存在时,创建空文件file。
$ cat < file >> file #分别以读、写添加的方式打开file,先读入file,如果file不为空,会将file的内容无限循环多次追加写入至file之中,直至磁盘无法写入为止。因为每次追加写入至file之中,file内容翻倍,此时 cat < file 会发现文件还没有到结束处,于是又追加写入。每次的写入量为1,2,4,8,……倍file最初的内容。

11.5. 管道与重定向

使用管道将前一个命令的标准输出(默认不含标准错误信息,除非将其合并 2>&1)送入后一个命令做输入。

如何将前一个命令输出信息输出值文件同时还将其送入给后续命令做输入呢?有两种方法:

$ cmd1 | cmd2 > file; cmd3 <file #但是此方法file的IO有两次
$ cmd1 | cmd2 | tee file | cmd3  #用tee命令来过河不拆桥/过渡:tee命令在不影响以前IO的情况下赋值一份stdin至file之中。

12、用if还是case

12.1 if语句

语法格式(if then是必须的,else部分可以不用写,如then不想执行任何命令可以用:冒号语句—空命令替代):

if cmd1
then
	cmd2
	cmd3
elif
then
	cmd4
	cmd5
else
	cmd6
	cmd7
fi

或者

if cmd1; then
	cmd2
elif cmd3; then
	cmd4
else
	cmd5
fi

12.2 case语句

使用场景:如在函数中判断某个string的值(可能有多个取值),如果用if,得用多个复合条件组合判断,代码不直观(当然也可以用正则表达式来判断)。举例:

QQ() {
    echo -n "Do you want to continue? (YES/NO):"
    read YN
    if [ "$YN" = Y -o "$YN" = y -o "$YN" = "Yes" -o "$YN" = "yes" -o "$YN" = "YES" ]
    then
    	QQ
    else
    	exit 0
    fi
}

换用正则表达式来简化条件测试:

...
if echo "$YN"|grep -q '^[Yy][Ee][Ss]*$'
...

采用 case 变量 in 范围 的语句,代码更直观:

QQ() {
    echo -n "Do you want to continue? (YES/NO):"
    read YN
    case "$YN" in
    	[Yy]|[Yy][Ee][Ss])
        QQ
    	;;
    *)
    	exit 0
    	;;
    esac
}

在Linux系统/etc/init.d/*目录中有很多script中采用了case判断,常见代码是这样的:

case "$1" in
	start)
		start
		;;
	stop)
		stop
		;;
	restart|reload)
		restart
		;;
	status)
		status
		;;
	*)
		echo $"Usage: $0 {start|stop|restart|status}"
		exit 1		

13、loop:for、while、until

13.1 for语句

for var in value1 value2 ... value_list 
do
	cmd1
	cmd2
done
#或者
for var; do
    ....
done

for loop 用于处理清单(list)项目非常方便,其清单除了可明确指定或从位置参数中获取之外,也可从变量替换或命令替换取得。对于一些累计变化"的项目(如整数加减),for 也适合处理:

for ((i=1;i<=10;i++)) # 注意整数表达式 采用的是双括号
do
   echo "num is $i"
done

13.2 while语句

num=1
while [ "$num" -le 10 ]; do
 	echo "num is $num"
    num=$(($num + 1))
done

while执行取决于[]内的条件测试结果,下述为无限循环的一种写法。

while :; do  #:冒号语句是个空指令,永远返回0(true)
	echo looping ...
done

13.3 until语句

num=1
until [ ! "$num" -le 10 ]; do
    echo "num is $num"
    num=$(($num + 1))
done

与while不同的是,在[]条件为false的情况下进入循环,当条件为真时,结束循环。

13.4 continue 和 break

break用来强制结束某层循环。若break后面指定一个数值n,则是从内向外打断第n层循环,默认n=1,指结束当前循环。与return和exit的区别如下:

  • break 用来结束loop
  • return 用来结束function
  • exit 用来结束script/shell

continue则与break相反:它强制程序马上进入下一次循环。同样,continue之后也可以带数值n,默认为1,以决定从哪一层循环继续。

14、理解[!]和[^]的差异

注:此处[]不是测试命令。

即理解Shell中的通配符wildcard和通用的正则表达式RE的表达方法。

15、eval的作用

Sunflower

Stay hungry stay foolish

Comments