💎一站式轻松地调用各大LLM模型接口,支持GPT4、智谱、星火、月之暗面及文生图 广告
##shell十三问之4:""(双引号)与''(单引号)差在哪? ------------------------------------------------ 还是回到我们的`command line`来吧... 经过前面两章的学习,应该很清楚当你在`shell prompt`后面敲打键盘, 直到按下`Enter`键的时候,你输入的文字就是`command line`了, 然后`shell`才会以进程的方式执行你所交给它的命令。 但是,你又可知道:你在`command line`中输入的每一个文字, 对`shell`来说,是有类别之分的呢? 简单而言,(我不敢说精确的定义,注1), `command line`的每一个`charactor`, 分为如下两种: - literal:也就是普通的纯文字,对`shell`来说没特殊功能; - meta: 对`shell`来说,具有特定功能的特殊保留元字符。 > **Note:** > 对于`bash shell`在处理`comamnd line`的顺序说明, > 请参考O'Reilly出版社的**Learning the Bash Shell,2nd Edition**, > 第177-180页的说明,尤其是178页的流程图:Figure 7-1 ... `literal`没什么好谈的, 像abcd、123456这些"文字"都是literal...(so easy? ^_^) 但meta却常使我们困惑...(confused?) 事实上,前两章,我们在`command line`中已碰到两个 似乎每次都会碰到的meta: - `IFS`:有`space`或者`tab`或者`Enter`三者之一组成(我们常用space) - `CR`: 由`Enter`产生; `IFS`是用来拆解`command line`中每一个词(word)用的, 因为`shell command line`是按词来处理的。 而`CR`则是用来结束`command line`用的,这也是为何我们敲`Enter`键, 命令就会跑的原因。 除了常用的`IFS`与`CR`, 常用的meta还有: |meta字符| meta字符作用| |--------|-------------| |= |设定变量| |$ | 作变量或运算替换(请不要与`shell prompt`混淆)|命令 |>| 输出重定向(重定向stdout)| |<|输入重定向(重定向stdin)| |\||命令管道| |&|重定向file descriptor或将命令至于后台(bg)运行| |()|将其内部的命令置于nested subshell执行,或用于运算或变量替换| |{}|将期内的命令置于non-named function中执行,或用在变量替换的界定范围| |;|在前一个命令执行结束时,而忽略其返回值,继续执行下一个命令| |&&|在前一个命令执行结束时,若返回值为true,继续执行下一个命令| |\|\||在前一个命令执行结束时,若返回值为false,继续执行下一个命令| |!|执行histroy列表中的命令| |... | ...| 假如我们需要在`command line`中将这些保留元字符的功能关闭的话, 就需要quoting处理了。 在`bash`中,常用的quoting有以下三种方法: - hard quote:''(单引号),凡在hard quote中的所有meta均被关闭; - soft quote:""(双引号),凡在soft quote中大部分meta都会被关闭,但某些会保留(如$); - escape: \ (反斜杠),只有在紧接在escape(跳脱字符)之后的单一meta才被关闭; > **Note:** > 在soft quote中被豁免的具体meta清单,我不完全知道, > 有待大家补充,或通过实践来发现并理解。 下面的例子将有助于我们对quoting的了解: ```shell $ A=B C #空白符未被关闭,作为IFS处理 $ C:command not found. $ echo $A $ A="B C" #空白符已被关掉,仅作为空白符 $ echo $A B C ``` 在第一个给A变量赋值时,由于空白符没有被关闭, `command line` 将被解释为: `A=B 然后碰到<IFS>,接着执行C命令` 在第二次给A变量赋值时,由于空白符被置于soft quote中, 因此被关闭,不在作为`IFS`; `A=B<space>C` 事实上,空白符无论在soft quote还是在hard quote中, 均被关闭。Enter键字符亦然: ```shell $ A=`B > C > ' $ echo "$A" B C ``` 在上例中,由于`enter`被置于hard quote当中,因此不再作为`CR`字符来处理。 这里的`enter`单纯只是一个断行符号(new-line)而已, 由于`command line`并没得到`CR`字符, 因此进入第二个`shell prompt`(`PS2`, 以>符号表示), `command line`并不会结束,直到第三行, 我们输入的`enter`并不在hard quote里面, 因此没有被关闭, 此时,`command line`碰到`CR`字符,于是结束,交给shell来处理。 上例的`Enter`要是被置于soft quote中的话,`CR`字符也会同样被关闭: ```shell $ A="B > C > " $ echo $A B C ``` 然而,由于 `echo $A`时的变量没有置于soft quote中, 因此,当变量替换完成后,并作命令行重组时,`enter`被解释为`IFS`, 而不是new-line字符。 同样的,用escape亦可关闭CR字符: ```shell $ A=B\ > C\ > $ echo $A BC ``` 上例中的,第一个`enter`跟第二个`enter`均被escape字符关闭了, 因此也不作为`CR`来处理,但第三个`enter`由于没有被escape, 因此,作为`CR`结束`command line`。 但由于`enter`键本身在shell meta中特殊性,在 \ escape字符后面 仅仅取消其`CR`功能, 而不保留其IFS功能。 你或许发现光是一个`enter`键所产生的字符,就有可能是如下这些可能: - CR - IFS - NL(New Line) - FF(Form Feed) - NULL - ... 至于,什么时候解释为什么字符,这个我就没法去挖掘了, 或者留给读者君自行慢慢摸索了...^-^ 至于soft quote跟hard quote的不同,主要是对于某些meta的关闭与否,以$来做说明: ```shell $ A=B\ C $ echo "$A" B C $ echo '$A' $A ``` 在第一个`echo`命令行中,$被置于soft quote中,将不被关闭, 因此继续处理变量替换, 因此,`echo`将A的变量值输出到屏幕,也就是"B C"的结果。 在第二个`echo`命令行中,$被置于hard quote中,则被关闭, 因此,$只是一个$符号,并不会用来做变量替换处理, 因此结果是$符号后面接一个A字母:$A. > **练习与思考:** 如下结果为何不同? > tips: 单引号和双引号,在quoting中均被关闭了。 ```shell $ A=B\ C $ echo '"$A"' #最外面的是单引号 "$A" $ echo "'$A'" #最外面的是双引号 'B C' ``` ------------------------------------ 在CU的shell版里,我发现很多初学者的问题, 都与quoting的理解有关。 比方说,若我们在awk或sed的命令参数中, 调用之前设定的一些变量时,常会问及为何不能的问题。 要解决这些问题,关键点就是:**区分出 shell meta 与 command meta** 前面我们提到的那些meta,都是在command line中有特殊用途的, 比方说{}就是将一系列的command line置于不具名的函数中执行(可简单视为command block), 但是,awk却需要用{}来区分出awk的命令区段(BEGIN,MAIN,END). 若你在command line中如此输入: ```shell $ awk {print $0} 1.txt ``` 由于{}在shell中并没有关闭,那shell就将{print $0}视为command block, 但同时没有`;`符号作命令分隔,因此,就出现awk语法错误结果。 要解决之,可用hard quote: ```shell awk '{print $0}' ``` 上面的hard quote应好理解,就是将原来的 {、<space>、$、}这几个shell meta关闭, 避免掉在shell中遭到处理,而完整的成为awk的参数中command meta。 > **Note:** > awk中使用的$0 是awk中内建的field nubmer,而非awk的变量, > awk自身的变量无需使用$. 要是理解了hard quote的功能,在来理解soft quote与escape就不难: ```shell awk "{print \$0}" 1.txt awk \{print \$0\} 1.txt ``` 然而,若要你改变awk的$0的0值是从另一个shell变量中读进呢? 比方说:已有变量$A的值是0, 那如何在`command line`中解决 awk的$$A呢? 你可以很直接否定掉hard quote的方案: ```shell $ awk '{print $$A}' 1.txt ``` 那是因为$A的$在hard quote中是不能替换变量的。 聪明的读者(如你!),经过本章的学习,我想,你应该可以理解为 为何我们可以使用如下操作了吧: ```shell A=0 awk "{print \$$A}" 1.txt awk \{print\ \$$A\} 1.txt awk '{print $'$A'}' 1.txt awk '{print $'"$A"'}' 1.txt ``` 或许,你能给出更多方案... ^_^ 更多练习: - http://bbs.chinaunix.net/forum/viewtopic.php?t=207178 一个关于read命令的小问题: 很早以前觉得很奇怪:执行read命令,然后读取用户输入给变量赋值, 但如果输入是以空格键开始的话,这空格会被忽略,比如: ```shell read a #输入: abc echo "$a" #只输出abc ``` 原因: 变量a的值,从终端输入的值是以IFS开头,而这些IFS将被shell解释器忽略(trim)。 应该与shell解释器分词的规则有关; ```shell read a #输入:\ \ \ abc echo "$a" #只输出abc ``` 需要将空格字符转义 > **Note:** > IFS Internal field separators, normally space, tab, and newline (see Blank Interpretation section). > ...... > Blank Interpretation > After parameter and command substitution, the results of substitution > are scanned for internal field separator characters (those found in IFS) > and split into distinct arguments where such characters are found. > Explicit null arguments ("" or '') are retained. > Implicit null arguments(those resulting from parameters that have no values) > are removed. > (refre to: man sh) 解决思路: 1. shell command line 主要是将整行line给分解(break down)为每一个单词(word); 2. 而词与词之间的分隔符就是IFS (Internal Field Seperator)。 3. shell会对command line作处理(如替换,quoting等), 然后再按词重组。(注:别忘了这个重组特性) 4. 当你用IFS来事开头一个变量值,那shell会先整理出这个词,然后在重组command line。 5.然而,你将IFS换成其他,那shell将视你哪些space/tab为“词”,而不是IFS。那在重组时,可以得到这些词。 若你还是不理解,那来验证一下下面这个例子: ```shell $ A=" abc" $ echo $A abc $ echo "$A" #note1 abc $ old_IFS=$IFS $ IFS=; $ echo $A abc $ IFS=$old_IFS $ echo $A abc ``` > **Note:** > 1. 这里是用 soft quoting 将里面的 space 关闭,使之不是 meta(IFS), > 而是一个literal(white space); > 2. IFS=; 意义是将IFS设置为空字符,因为;是shell的元字符(meta); 问题二:为什么多做了几个分号,我想知道为什么会出现空格呢? ```shell $ a=";;;test" $ IFS=";" $ echo $a test $ a=" test" $ echo $a test $ IFS=" " $ echo $a test ``` 解答: 这个问题,出在`IFS=;`上。 因为这个`;`在问题一中的command line上是一个meta, 并非`";"`符号本身。 因此,`IFS=;`是将IFS设置为 null charactor (不是space、tab、newline)。 要不是试试下面这个代码片段: ```shell $ old_IFS=$IFS $ read A ;a;b;c $ echo $A ;a;b;c $ IFS=";" #Note2 $ echo $A a b c ``` > **Note:** > 要关闭`;`可用`";"`或者`';'`或者`\;`。 - http://bbs.chinaunix.net/forum/viewtopic.php?t=216729 思考问题二:文本处理:读文件时,如何保证原汁原味。 ```shell cat file | while read i do echo $i done ``` 文件file的行中包含若干空,经过read只保留不重复的空格。 如何才能所见即所得。 ```shell cat file | while read i do echo "X${i}X" done ``` 从上面的输出,可以看出read,读入是按整行读入的; 不能原汁原味的原因: 1. 如果行的起始部分有IFS之类的字符,将被忽略; 2. `echo $i`的解析过程中,首先将$i替换为字符串, 然后对echo 字符串中字符串分词,然后命令重组,输出结果; 在分词,与命令重组时,可能导致多个相邻的IFS转化为一个; ```shell cat file | while read i do echo "$i" done ``` 以上代码可以解决原因2中的,command line的分词和重组导致meta字符丢失; 但仍然解决不了原因1中,read读取行时,忽略行起始的IFS meta字符。 回过头来看上面这个问题:为何要原汁原味呢? cat命令就是原汁原味的,只是shell的read、echo导致了某些shell的meta字符丢失; 如果只是IFS meta的丢失,可以采用如下方式: 将IFS设置为null,即`IFS=;`, 在此再次重申此处`;`是shell的meta字符,而不是literal字符; 因此要使用literal的 `;`应该是`\;` 或者关闭meta 的(soft/hard) quoting的`";"`或者`';'`。 因此上述的解决方案是: ```shell old_IFS=$IFS IFS=; #将IFS设置为null cat file | while read i do echo "$i" done IFS=old_IFS #恢复IFS的原始值 ``` 现在,回过头来看这个问题,为什么会有这个问题呢; 其本源的问题应该是没有找到解决原始问题的最合适的方法, 而是采取了一个迂回的方式来解决了问题; 因此,我们应该回到问题的本源,重新审视一下,问题的本质。 如果要精准的获取文件的内容,应该使用od或者hexdump会更好些。