天天看點

為什麼shell中要注意檔案的命名方式

作者:SuperOps
為什麼shell中要注意檔案的命名方式

‬問題背景

所有常用的檔案複制工具(如ssh、scp、rsync)都會将檔案名作為shell指令的一部分發送給遠端系統進行解釋。這使得問題變得非常複雜,因為遠端shell通常會破壞檔案名。

‬解決方案

處理該問題至少存在三種方法:

  • NFS
  • 對檔案名進行适當編碼
  • 将檔案名作為資料流的一部分進行送出

首先讓我們看看不起作用的方法:

# Will not work
scp "my file" remote:"your file"           

scp基本上是在ssh之上的一個薄包裝層,它通過訓示遠端系統的shell打開一個寫入檔案來工作。由于檔案名以最簡單的方式傳遞給遠端shell,遠端shell将空格視為參數分隔符,并最終建立一個名為your的檔案。

類似的問題很多人使用錯誤的解決方法,嘗試使用其他工具解決該問題:

# Will not work
ssh remote cat \> "your file" < "my file"
           
# Will not work
rsync "my file" remote:"your file"
           

那麼,什麼方法有效?

NFS

如果你使用NFS(或任何其他能力強大的網絡檔案系統共享技術,包括sshfs,甚至可能是smbfs)将遠端主機的檔案系統挂載到本地主機上,那麼你可以直接進行複制操作:

cp "my file" /remote/"your file"

謹慎地對遠端名稱進行編碼

顯然,如果在編寫指令時已經知道遠端名稱,可以以一種能夠被遠端shell解析的方式進行編碼。通常這意味着添加一個額外的引号層。例如,以下方法有效:

scp "my file" remote:"'your file'"
           

但是在一般情況下,我們無法在編寫腳本時知道确切的遠端檔案名。它将作為參數、環境變量等傳遞給腳本。在這種情況下,我們必須足夠聰明地編碼任何可能的檔案名。

問題進一步複雜化的原因是我們不一定知道遠端使用者使用的是哪個shell。你在客戶工作站上使用bash,并不意味着遠端系統的sshd會生成bash來解析你的指令。(請記住,scp通過ssh發送一個shell指令,某個未知的遠端shell需要解析該指令。)是以,我們使用的任何解決方案都必須盡可能與shell無關。例如,bash的printf %q就被排除在外。

在這些限制下,唯一剩下的方法是在整個檔案名周圍加上單引号。這意味着我們還必須修改檔案名中已有的任何單引号。是以,我們的編碼如下所示:

q=\'
dest="'${dest//$q/$q\\$q$q}'"
           

這樣我們得到一個修改後的dest,其起始和結束處有文字意義的單引号,并且用''替換了所有内部的'字元。當此内容傳遞給遠端shell進行解析時,結果就是我們最初的檔案名。

是以,完整的複制函數可能如下所示:

# copyto <sourcefile> <remotehost> <remotefile>
copyto() {
    local q dest
    q=\'
    dest="'${3//$q/$q\\$q$q}'"
    scp "$1" "$2":"$dest"
}
           

通過資料流發送檔案名

這種方法稍微不太便攜,因為它要求遠端主機上安裝了bash(盡管不一定是遠端使用者的登入Shell)。它是一種更通用的解決方案,因為理論上可以傳遞任何類型的資料流,隻要您能編寫一個解析器來解析它(但請記住,您必須将解析器發送到遠端系統進行執行,是以它需要簡單)。

在這個示例中,我們将發送一個資料流,其中包含兩個内容:檔案名和檔案的内容。它們将由空位元組(NUL byte)分隔。我們使用bash在遠端系統上解析此資料流,因為它是極少數幾個可以解析NUL分隔的資料流的shell之一。

# copyto <sourcefile> <remotehost> <remotefile>
copyto() {
    { printf '%s\0' "$3"; cat < "$1"; } |
    ssh "$2" bash -c \''read -rd ""; cat > "$REPLY"'\'
}
           

我們的解析器是bash指令read -rd ""; cat > "$REPLY"。它會讀取檔案名(以NUL結尾)并将其存儲在shell變量REPLY中,然後調用cat讀取流的剩餘部分。解析器周圍有兩層引号,因為我們需要對本地shell和遠端shell進行引用。是以,我們避免在解析器中使用單引号,在本地層使用單引号,并在遠端層使用轉義的單引号。

這個版本不使用scp,是以不會複制檔案的權限。如果你想做到這一點,你可以将權限作為資料流中的另一個對象傳遞,解析出來并調用chmod。(擷取本地檔案權限沒有通用的方法,是以那實際上是最難的部分。)

‬最後

如果你想學習如何編寫更加健壯和可靠的 Shell 腳本,減少生産環境中的錯誤和故障,那麼關注我吧!我會分享 Shell 程式設計的最佳實踐和建議,幫助你提高 Shell 腳本的魯棒性和可維護性。如果你想深入了解 Shell 程式設計的實際應用和技巧,可以關注我的《Shell 腳本程式設計最佳實踐》專欄,裡面有我在一線網際網路大廠的實際生産經驗和最佳實踐,幫助你高效完成各種自動化任務。