­

一個shell中詭異問題的解決

  • 2019 年 12 月 25 日
  • 筆記

前兩天上線,同事碰見個shell腳本的問題,當時場景,異常詭異。

腳本的判斷邏輯很長,為了方便說明,此處只給出關鍵步驟,

a=`curl ...` (期待返回值是AA)  b=BB  echo $a$$b (期待輸出是AA$BB,實際輸出$BB)  echo $b$$a (實際輸出BB$AA)

其中,變量a是通過curl以及一系列管道操作截取的信息,使用echo打印多個變量的值,非常詭異的是,出現了一些字符沒有出現或者從首字符處出現了疊寫、相互覆蓋、覆寫的現象,例如$a$$b,$b重疊到首字母進行覆蓋輸出,甚至相同的兩個變量,互換順序,結果顯示不同。

這個時候,sh命令的-x這個參數,就開始起作用了,他的意思是顯示shell執行過程中的命令,通過執行,我們看到了區別,變量a的值,不是期待的AA,而是"AAr",

+ a='AAr'

不要小瞧r,我們知道Windows下的空行是rn,,但是linux下是n,沒有識別r。

n是LF或ASCII中的0x0A(10),r是CR或ASCII中的0x0D(13)。

問題來了,LF和CR是什麼?這就要講講「回車」(carriage return)和「換行」(line feed)這兩個概念的來歷和區別了。

在計算機還沒有出現之前,有一種叫做電傳打字機(Teletype Model 33)的玩意,每秒鐘可以打10個字符。但是有一個問題,就是打完一行換行的時候,要用去0.2秒,正好可以打兩個字符。要是在這0.2秒裏面,又有新的字符傳過來,那麼這個字符將丟失。

於是,研製人員想了個辦法解決這個問題,就是在每行後面加兩個表示結束的字符。一個叫做「回車」,告訴打字機把打印頭定位在左邊界,另一個叫做「換行」,告訴打字機把紙向下移一行。

這就是「換行」和「回車」的來歷,從它們的英語名字上也可以看出一二,CR就是carriage return的縮寫,LF是line feed的縮寫。

後來,計算機發明了,這兩個概念也就被般到了計算機上。那時,存儲器很貴,一些科學家認為在每行結尾加兩個字符太浪費了,加一個就可以。於是,就出現了分歧。

Unix系統里,每行結尾只有「<換行>」,即「n」,Windows系統裏面,每行結尾是「<換行><回車>」,即「nr」,Mac系統里,每行結尾是「<回車>」。一個直接後果是,Unix/Mac系統下的文件在Windows里打開的話,所有文字會變成一行,而Windows里的文件在Unix/Mac下打開的話,在每行的結尾可能會多出一個^M符號。

因此,r就是return回到本行行首,這就會把這一行以前的輸出覆蓋掉。

$a$$b,其中$a='AAr',所以$b重疊到首字母進行覆蓋輸出,就可以解釋了。

我們用下面的腳本模擬下,可能會更清晰,

#!/bin/bash    a='SEr'  b='SA'  echo -e $a$b  echo -e $a$$b  echo -e $b$a  echo -e $b$$a

可以看到變量a='SEr',然後使用echo -e參數,他的作用是enable interpretation of backslash escapes,即啟用反斜杠轉義功能,

+ a='SEr'  + b=SA  + echo -e 'SErSA'  SA  + echo -e 'SEr$SA'  $SA  + echo -e 'SASEr'  SASE  + echo -e 'SA$SEr'  SA$SE

因為r的存在,$a$b輸出的是$b的值,如果是$b$a,因為r放在後面,並未影響,所以是正確的$b$a。

既然明確了問題,解決就很清晰了,可以在curl加個tr -d,就達到了刪除r,保證對的換行,

a=`curl ... | tr -d 'r'`

在科學技術上,任何你看到的現象都會有他的原因,所謂存在即合理,詭異的事情,還是因為沒看到本質,其實根本不詭異。