shell編程中星號(asterisk "*")的坑
- 2020 年 3 月 11 日
- 筆記
今天分享一個有關shell編程中由通配符引起的問題。
1. 問題程式碼
cat test.logs
4567890 * ##*************************************## rtyuio**tyuio432 ##*************************************## *rtyuiop*2* yuiop ##*************************************## rtyuiop(3 * 4)iuytr ##*************************************## 8765432 cat script.sh
#!/usr/bin/env bash # 主要功能是將 非##開頭 的每行記錄寫入到文件中,每個文件保存一行記錄 logsname=test.logs i=100 while read line do if [[ $line =~ '##' ]];then ((i++)) else echo $line >> $i.txt fi done < "${logsname}" 運行script.sh腳本的結果:

從圖片上紅框部分可以看到:
4567890 *被替換為4567890 script.sh test.logs
rtyuiop(3 * 4)iuytr被替換為rtyuiop(3 100.txt 101.txt 102.txt script.sh test.logs 4)iuytr
其他行都正常列印結果,為什麼這兩行會有問題呢?其他行也有星號,為什麼沒有被替換呢?
2. 原因分析
根據輸出結果可以判斷出問題的程式碼:echo $line >> $i.txt
首先,介紹一下shell執行腳本的原理:
- shell讀取整個腳本文件,然後從上到下依次執行每一行
- 假設當前line=4567890 *,當shell執行
echo $line >> $i.txt時- 首先,shell負責替換
$line的值為:4567890 *,此時程式碼是:echo 4567890 * >> $i.txt - 然後,shell在執行echo命令之前,檢查命令的參數中是否有通配符(PS:此時echo的參數:4567890 *)
- 很明顯,
*是通配符,shell負責解析通配符,shell會將通配符當作路徑或文件名在磁碟上搜尋可能的匹配:若符合要求的匹配存在,則進行替換(路徑擴展);否則就將該通配符作為一個普通字元參數傳遞給echo,然後再由echo進行處理。 - 解析完通配符後,
*被替換為script.sh test.logs,此時echo命令的參數是:4567890 script.sh test.logs - 最後,shell執行
echo 4567890 script.sh test.logs,然後將echo命令執行的結果重定向到文件中。
- 首先,shell負責替換
Tips:
通配符看起來有點像正則表達式,但是它與正則表達式不同的,不能相互混淆。可以把通配符理解為shell能夠處理的特殊字元。而且shell的通配符涉及的只有 "*, ?, [], {}" 這幾種。
3. 解決方案
根據上面的分析可以知道,$line被替換後,通配符*再次被shell解析。哪有什麼辦法可以防止shell解析通配符呢?
- 使用引用變數:
"$line",在兩端加上引號,這樣"$line"就變成了一個字元串"4567890 *",而不是兩個單獨的字元串。 - 引用變數可防止分詞和通配符擴展(也就是shell解析通配符),並且可以防止在變數中包含空格、換行符、通配符等時造成腳本中斷。
- 在shell編程中總是使用引用變數的方式,這是一個良好又安全的編碼習慣。
4. 參考資料
- Reading asterisk character (*) from a file in bash
- When to wrap quotes around a shell variable?
- Security implications of forgetting to quote a variable in bash/POSIX shells
- shellcheck.net-finds bugs in your shell scripts.
- Linux Shell 通配符、元字元、轉義符使用實例介紹

