深爱那片海 发表于 2015-1-18 11:25:27

Linux设计Bash编程易犯的毛病(一)仓酷云

常常有些朋友在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文件名中不会包括空格大概*号。不然,这行命令实行前在经由单词拆分和文件名睁开的时分会呈现成绩。以是,两次夸大,在利用睁开的中央切勿健忘利用引号:
$cp--"$file""$target"假如不带引号,当你实行以下命令时就会堕落:
$file="01-DontEattheYellowSnow.mp3"$target="/tmp"$cp$file$targetcp:cannotstat‘01’:Nosuchfileordirectory..假如带上引号,就不会有下面的成绩,除非文件名以-开首,在这类情形下,cp会以为你供应的是一个命令行选项,这个毛病上面会先容。
3.文件名中包括短横-

文件名以-开首会招致很多成绩,*.mp3这类通配符会依据以后的locale睁开成一个列表,但在尽年夜多半情况下,-排序的时分会排在年夜多半字母前。这个睁开的列表传送给有些命令的时分,会毛病的将-filename剖析成命令行选项。这里有两种办法来办理这个成绩。
第一种办法是在命令和参数之间加上--,这类语法告知命令不要持续对--以后的内容举行命令行参数/选项剖析:
$cp--"$file""$target"这类办法能够解这个成绩,可是你必要在每一个命令前面都要加上--,并且依附详细的命令剖析的体例,假如一些命令不兼容这类商定俗成的标准,这类做法是有效的。
别的一种办法是,确保文件名都利用绝对大概相对的路径,以目次开首:
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.mp377.[[$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

初学者会毛病地以为,[是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;then...

正如上一个成绩中提到的,[是一个命令,它的参数之间必需用空格分开。
11.if[&&];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中,用户权限很大,做任何事情都很自由,所以,你往往需要知道你做的每一步在干什么。

蒙在股里 发表于 2015-1-21 06:07:59

如果你有庞大而复杂的测试条件,尽量把它剪裁得越小越好。可能你会遇到这种情况,对于一个问题会出现不同内容回答,这时你需要通过实践来验证。

深爱那片海 发表于 2015-1-30 09:17:13

其实当你安装了一个完整的Linux系统后其中已经包含了一个强大的帮助,只是可能你还没有发现和使用它们的技巧。

仓酷云 发表于 2015-1-31 12:04:24

其实老师让写心得我也没怎么找资料应付,自己想到什么就写些什么,所以不免有些凌乱;很少提到编程,因为那些在实验报告里已经说了,这里再写就多余了。

只想知道 发表于 2015-2-6 19:11:44

Linux最大的特点就是其开源性,这一点是十分难得的,这也是它能够存在到现在的原因之一。

山那边是海 发表于 2015-2-11 20:09:41

其实当你安装了一个完整的Linux系统后其中已经包含了一个强大的帮助,只是可能你还没有发现和使用它们的技巧。

若相依 发表于 2015-2-24 20:53:19

老实说,第一个程序是在C中编译好的,调试好了才在Linux下运行,感觉用vi比较麻烦,因为有错了不能调试,只是提示错误。

金色的骷髅 发表于 2015-2-28 18:25:24

一定要养成在命令行下工作的习惯,要知道X-window只是运行在命令行模式下的一个应用程序。在命令行下学习虽然一开始进度较慢。

小魔女 发表于 2015-3-10 03:33:37

清楚了解网络的基础知识,特别是在Linux下应用知识,如接入internet等等。

活着的死人 发表于 2015-3-11 09:28:10

和私有操作系统不同,各个Linux的发行版本的技术支持时间都较短,这对于Linux初学者是往往不够的。

海妖 发表于 2015-3-11 19:53:12

放手去搞。尽量不要提问,运用搜索找答案,或者看wiki,从原理上理解操作系统的本质,而不是满足于使用几个技巧。尽量看英文资料。

飘灵儿 发表于 2015-3-19 10:40:51

我想即使Linux高手也很难快速准确精练的回答你。

老尸 发表于 2015-3-27 18:08:31

硬盘安装及光盘安装,清楚了解安装Linux应注意的有关问题,如安装Linux应在最后一个分区内,至少分二个分区。
页: [1]
查看完整版本: Linux设计Bash编程易犯的毛病(一)仓酷云