當我們使用 Windows 桌面下的編輯器編寫一個 Shell 檔案時,很容易將檔案使用的換行符儲存為 dos 格式。如果將檔案上傳到 Linux 伺服器執行時,可能會遇到下面的錯誤。這是因為
# 顯示一個簡單的shell檔案
$ cat dosnewline.sh
#!/bin/sh
echo "This is a file with dos newline"
# 該檔案使用了 dos 格式的換行符
$ od -bc dosnewline.sh
0000000 043 041 057 142 151 156 057 163 150 015 012 015 012 145 143 150
# ! / b i n / s h \r \n \r \n e c h
0000020 157 040 042 124 150 151 163 040 151 163 040 141 040 146 151 154
o " T h i s i s a f i l
0000040 145 040 167 151 164 150 040 144 157 163 040 156 145 167 154 151
e w i t h d o s n e w l i
0000060 156 145 042 015 012 015 012 015 012
n e " \r \n \r \n \r \n
0000071
# 使用 sh 執行的時候就會有一個報錯
$ h dosnewline.sh
: command not found 2:
This is a file with dos newline
: command not found 4:
: command not found 5:
# 獲取指令碼的返回碼也不是0,在一些自動化呼叫的場景中就會認為指令碼執行失敗,從而引發後續的問題
$ echo $?
127
# 退出碼 127 的意思是 command not foud,對應具體的 dos 換行符所在的行
我們通常所說的換行符在 ASCII 碼錶中對應下面兩個字元。
十進位制 | 十六進位制 | 字元 | 程式設計時 |
---|---|---|---|
10 | A | LF(Line feed,New Line) | \n |
13 | D | CR(Carriage return) | \r |
這兩個字元被用作換行的標誌,但是在不同作業系統中使用的不一樣,具體如下:
作業系統 | 換行符 |
---|---|
Unix(包括 Linux) | \n |
Windows | \r\n |
MacOS X 之前的版本 | \r |
MacOS X 及之後的版本 | \n |
為什麼 Windows 中會用兩個字元而其他系統使用一個字元呢?
據說很久以前,人們在使用老式電傳打字機作為輸入裝置的年代,這種裝置內部使用兩個字元來另起新行。一個字元把滑動架移回首位 (稱為回車),另一個字元把紙上移一行 (稱為換行)。
當電子計算機問世後,由於記憶體曾經非常昂貴。有些人認定沒必要用兩個字元來表示行尾。於是 UNIX 開發者決定他們可以用一個字元(LF)來表示行尾,Apple 開發者規定了用 (CR)來表示行尾,而 MS-DOS(以及後來的 Windows)開發人員則沿用了老式的兩個字元 。
正是因為不同作業系統預設的換行符不同,導致在 Windows 下編寫的檔案採用了 Windows 下的換行符。而不幸的是 sh 做為 Linux 下的應用,只認識 Unix(包括 Linux)下的換行符,引發的文章開頭的問題。
解決的方法有很多,從指令碼來源上說,最好我們在編輯過程中就指定使用的換行符,大多數編碼常用編輯器例如 Notepadd++ 等都支援這個選項,如下圖在 Notepadd++ 的右下角會顯示換行符的型別。千萬不要使用 Windows 自帶的記事本來編寫 shell 指令碼,記事本是不支援調整換行符的。
除了在編寫階段注意,指令碼編寫完成後,還可以通過 $ sh -x hello.sh
的方式來檢查指令碼是否有語法錯誤,對於本文提供的範例來說輸出結果如下,可以看到輸出結果給出提示多了 \r
的字元。
$ sh -x dosnewline.sh
+ $'\r'
: command not found 2:
' echo 'This is a file with dos newline
This is a file with dos newline
+ $'\r'
: command not found 4:
+ $'\r'
: command not found 5:
最後如果不小心,這樣的指令碼已經進入了生產環境,也還有很多的方法來進行修改。很多文章推薦使用 dos2unix
這個命令來快速修改,這個命令使用起來比較方便,但是對於一些生產環境管理嚴格的單位來說,這個命令未必允許在生產環境安裝。
那就還可以用一般都有的 tr
、awk
、sed
命令來實現,下面給出具體範例。
$ tr -d '\r' < dosnewline.sh > dosnewline.sh-tr
# 使用 od 比較兩個檔案,後續的指令碼可類似方式比較
$ od -bc dosnewline.sh-tr
0000000 043 041 057 142 151 156 057 163 150 012 012 145 143 150 157 040
# ! / b i n / s h \n \n e c h o
0000020 042 124 150 151 163 040 151 163 040 141 040 146 151 154 145 040
" T h i s i s a f i l e
0000040 167 151 164 150 040 144 157 163 040 156 145 167 154 151 156 145
w i t h d o s n e w l i n e
0000060 042 012 012 012
" \n \n \n
0000064
$ od -bc dosnewline.sh
0000000 043 041 057 142 151 156 057 163 150 015 012 015 012 145 143 150
# ! / b i n / s h \r \n \r \n e c h
0000020 157 040 042 124 150 151 163 040 151 163 040 141 040 146 151 154
o " T h i s i s a f i l
0000040 145 040 167 151 164 150 040 144 157 163 040 156 145 167 154 151
e w i t h d o s n e w l i
0000060 156 145 042 015 012 015 012 015 012
n e " \r \n \r \n \r \n
0000071
$ awk '{ sub("\r$", ""); print }' dosnewline.sh > dosnewline.sh-awk
$ sed 's/\r//' dosnewline.sh > dosnewline.sh-sed
這篇文章首發在我的個人站點 大江小浪 上,更多內容,歡迎存取。