|
马上注册,结交更多好友,享用更多功能,让你轻松玩转社区。
您需要 登录 才可以下载或查看,没有帐号?立即注册
x
常常有些朋友在Linux论坛问一些问题,不过,其中大多数的问题都是很基的。
前一段工夫发明一个很好的wiki站点,下面有良多优异的Bash文章。比来挑了一篇先容Bash编程简单犯的各类毛病的文章看,劳绩良多,不感独享,把这篇文章以半翻译半条记的情势分享给人人。
1.foriin$(ls*.mp3)
Bash写轮回代码的时分,的确对照简单犯上面的毛病:- foriin$(ls*.mp3);do#毛病!somecommand$i#毛病!doneforiin$(ls)#毛病!foriin`ls`#毛病!foriin$(find.-typef)#毛病!foriin`find.-typef`#毛病!files=($(find.-typef))#毛病!foriin${files[@]}#毛病!
复制代码 这里次要两个成绩:
- 利用命令睁开时不带引号,其实行了局会利用IFS作为分开符,拆分红参数传送给for轮回处置;
- 不该该让剧本往剖析ls命令的了局;
我们不克不及制止某些文件名中包括空格,Shell会对$(ls*.mp3)睁开的了局会被做单词拆分(WordSplitting)的处置。假定有一个文件,名字为01-DontEattheYellowSnow.mp3,for轮回处置的时分,会今次遍历文件名中的每一个单词:01,-,Dont,Eat等等:- $foriin$(ls*.mp3);doecho$i;done01-DontEattheYellowSnow.mp3
复制代码 比这更差的情形是,下面命令睁开的了局大概被Shell进一步处置,好比文件名睁开。好比,ls实行的了局中包括*号,依照通配符的划定规矩,*号会被睁开成以后目次下的一切文件:- $touch"1*.mp3""1.mp3""11.mp3""12.mp3"$foriin$(ls*.mp3);doecho$i;done1*.mp31.mp311.mp312.mp31.mp311.mp312.mp31.mp311.mp312.mp3
复制代码 不外,在这类场景下,你即便加上引号,也是杯水车薪的:- $foriin"$(ls*.mp3)";doecho--$i--;done--1*.mp31.mp311.mp312.mp3--
复制代码 加上引号后,ls实行的了局会被当做一个全体,以是for轮回只会实行一次,达不到预期的效果。
现实上,这类情形下,基本不必要利用ls命令。ls命令的了局自己就计划成给人读的,而不是给剧本剖析的。准确的处置办法是,间接利用文件名睁开(通配符)的功效:- $foriin*.mp3;do>echo"$i">done1*.mp31.mp311.mp312.mp3
复制代码 文件名睁开是位于各类睁开(花括号睁开、变量交换、命令睁开等)功效中的最初一个环节,以是不会有之前不带引号的命令睁开的反作用。假如你必要递回地处置文件,能够思索利用Find命令。
到这一步,之间的成绩看模样已修复了。可是,假如你进一步思索,假定以后目次上没有文件时会怎样?没有文件的时分,*.mp3不会被睁开间接传送给for轮回处置,以是这个时分轮回仍是会实行一次。这类情形不是我们预期的举动。保险起见,能够在轮回处置的时分,反省下文件是不是存在:- #POSIXforiin*.mp3;do[-e"$i"]||continuesomecommand"$i"done
复制代码 假如你有利用引号和制止单词拆分的习气,你完整能够制止良多毛病。
注重下轮回体外部的"$i",这里会招致上面我们要说的别的一个对照简单犯的毛病。
2.cp$file$target
下面的命令有甚么成绩呢?假如你提早晓得,$file和$target文件名中不会包括空格大概*号。不然,这行命令实行前在经由单词拆分和文件名睁开的时分会呈现成绩。以是,两次夸大,在利用睁开的中央切勿健忘利用引号:假如不带引号,当你实行以下命令时就会堕落:- $file="01-DontEattheYellowSnow.mp3"$target="/tmp"$cp$file$targetcp:cannotstat‘01’:Nosuchfileordirectory..
复制代码 假如带上引号,就不会有下面的成绩,除非文件名以-开首,在这类情形下,cp会以为你供应的是一个命令行选项,这个毛病上面会先容。
3.文件名中包括短横-
文件名以-开首会招致很多成绩,*.mp3这类通配符会依据以后的locale睁开成一个列表,但在尽年夜多半情况下,-排序的时分会排在年夜多半字母前。这个睁开的列表传送给有些命令的时分,会毛病的将-filename剖析成命令行选项。这里有两种办法来办理这个成绩。
第一种办法是在命令和参数之间加上--,这类语法告知命令不要持续对--以后的内容举行命令行参数/选项剖析:这类办法能够解这个成绩,可是你必要在每一个命令前面都要加上--,并且依附详细的命令剖析的体例,假如一些命令不兼容这类商定俗成的标准,这类做法是有效的。
别的一种办法是,确保文件名都利用绝对大概相对的路径,以目次开首:- foriin./*.mp3;docp"$i"/target...done
复制代码 这类情形下,即便某个文件以-开首,睁开后文件名仍然是./-foo.mp3这类情势,完整不会有成绩。
4.[$foo="bar"]
这是一个与第2个成绩相似的成绩,固然用到了引号,可是放错了地位,关于字符串字面值,除非有特别标记,不然不年夜必要用引号括起来。可是,你应当把变量的值用括号括起来,从而制止它们包括空格或能通配符,这一点我们在后面的成绩中都注释过。
这个例子在以下情形下会堕落:
- 假如[中的变量不存在,大概为空,这个时分下面的例子终极剖析了局是:
- $foriin$(ls*.mp3);doecho$i;done01-DontEattheYellowSnow.mp30
复制代码 而且实行会堕落:unaryoperatorexpected,由于=是二元操纵符,它必要摆布各一个操纵数。
- 假如变量值包括空格,它起首在实行之行进行单词拆分,因而[命令看到的模样多是如许的:
- $foriin$(ls*.mp3);doecho$i;done01-DontEattheYellowSnow.mp31
复制代码 准确的做法应当是:- $foriin$(ls*.mp3);doecho$i;done01-DontEattheYellowSnow.mp32
复制代码 这类写法,在POSIX兼容的完成中都不会有成绩,即便$foo以短横"-"开首,由于POSIX完成的test命令经由过程传送的参数来断定实行的举动。
只要一些十分陈旧的shell大概会碰到成绩,这个时分你可使用上面的写法来办理(信任你一定看到过这类写法):- $foriin$(ls*.mp3);doecho$i;done01-DontEattheYellowSnow.mp33
复制代码 在Bash中,另有别的一种选择是利用[[关头字:- $foriin$(ls*.mp3);doecho$i;done01-DontEattheYellowSnow.mp34
复制代码 这里你不必要利用引号,由于在[[内里参数不会举行睁开,固然带上引号也不会有错。
不外有一点要注重的是,[[里的==不单单是文本对照,它会反省右边的值是不是婚配右边的表达式,==右边的值加上引号,会让它成为一个一般的字面量,*?等通配符会得到特别寄义。
5.cd$(dirname"$f")
这又是一个引号的成绩,命令睁开的了局会进一步地举行单词拆分大概文件名睁开。因而上面的写法才是准确的:- $foriin$(ls*.mp3);doecho$i;done01-DontEattheYellowSnow.mp35
复制代码 可是,下面引号的写法大概对照奇异,你大概会以为第1、二个引号,第3、四个引号是一组的。
可是现实上,Bash将命令交换内里的引号当做一组,表面确当成别的一组。假如你是用反引号的写法,引号的举动就不是如许的了,以是$()写法加倍保举。
6.["$foo"=bar&&"$bar"=foo]
不要在test命令外部利用&&,Bash剖析器会把你的命令分开成两个命令,在&&之前和以后。你应当利用上面的写法:- $foriin$(ls*.mp3);doecho$i;done01-DontEattheYellowSnow.mp36
复制代码 只管制止利用上面的写法,固然它是准确的,可是这类写法可移植性欠好,而且已在POSIX-2008中被放弃:- $foriin$(ls*.mp3);doecho$i;done01-DontEattheYellowSnow.mp37
复制代码 7.[[$foo>7]]
原文作者以为算术对照不该该用[[,而是用((,我没弄分明是为何。
假如有了解的同砚,接待以批评复兴,感谢。
8.grepfoobar|whileread-r;do((count++));done
这类写法初看没有成绩,可是你会发明当实行完后,count变量并没有变更。缘故原由是管道前面的命令是在一个子Shell中实行的。
POSIX标准并没有申明管道的最初一个命令是否是在子Shell中实行的。一些shell,比方ksh93大概Bash>=4.2能够经由过程shopt-slastpipe命令,指明管道中的最初一个命令在以后shell中实行。因为篇幅限定,在此就不睁开,有乐趣的能够看BashFAQ#24。
9.if[grepfoomyfile]
初学者会毛病地以为,[是if语法的一部分,正如C言语中的if()。可是现实并不是云云,if前面随着的是一个命令,[是一个命令,它是内置命令test的简写情势,只不外它请求最初一个参数必需是]。上面两种写法是一样的:- $foriin$(ls*.mp3);doecho$i;done01-DontEattheYellowSnow.mp38
复制代码 两个都是反省参数"false"是否是非空的,以是下面两个语句城市输入HELP。
if语句的语法是:- $foriin$(ls*.mp3);doecho$i;done01-DontEattheYellowSnow.mp39
复制代码 再次夸大,[是一个命令,它同别的惯例的命令一样承受参数。if是一个复合命令,它包括别的命令,[并非if语法中的一部分。
假如你想依据grep命令的了局来办事情,你不必要把grep放到[内里,只必要在if前面紧跟grep便可:- $touch"1*.mp3""1.mp3""11.mp3""12.mp3"$foriin$(ls*.mp3);doecho$i;done1*.mp31.mp311.mp312.mp31.mp311.mp312.mp31.mp311.mp312.mp30
复制代码 假如grep在myfile中找到婚配的行,它的实行了局为0(true),then前面的部分就会实行。
10.if[bar="$foo"];then...
正如上一个成绩中提到的,[是一个命令,它的参数之间必需用空格分开。
11.if[[a=b]&&[c=d]];then...
不要用把[命令当作C言语中if语句的前提一样,它是一个命令。
假如你想表达一个复合的前提表达式,能够如许写:- $touch"1*.mp3""1.mp3""11.mp3""12.mp3"$foriin$(ls*.mp3);doecho$i;done1*.mp31.mp311.mp312.mp31.mp311.mp312.mp31.mp311.mp312.mp31
复制代码 注重,if前面有两个命令,它们用&&分隔。等价于上面的写法:
由于在linux中,用户权限很大,做任何事情都很自由,所以,你往往需要知道你做的每一步在干什么。 |
|