處理單一檔案作為整個管道的輸入和輸出

處理單一檔案作為整個管道的輸入和輸出

晚安,

我想使用一些管道命令過濾文件的內容,然後將結果寫回同一個文件。我知道,我不能按照我寫的方式做到這一點。堅持,稍等 …

這是我的 bash 腳本。

grep '^[a-zA-Z.:]' "$filepath" \
    | sed -r '/^(rm|cd)/d' \
    | uniq -u \
    > "$filepath"

所以我認為我可以成功地使用進程替換來代替。然後我寫道:

grep '^[a-zA-Z.:]' < <(cat "$filepath") | …

這也沒有解決任何問題。我希望進程替換能夠將我的輸入檔案內容「儲存」到某個地方,例如臨時檔案中。看來我也沒有理解進程替換。

我閱讀了有關“就地”版本的線程,但這些文章強調了一些二進位檔案的特殊選項,例如sed -i或 ,sort -o但我需要一個通用的解決方案(我的意思是它必須適合任何管道命令)。

首先,為什麼「標準管道方式」不能做到這一點,下面發生了什麼?:/我該如何解決我的問題?有人可以嗎解釋我這是怎麼回事?

謝謝。

答案1

正如已經提到的,海綿來自更多實用程式是很棒的。我使用此腳本進行模擬以避免 moreutils 依賴:

#!/bin/sh -e
#Soak up input and tee it to arguments
st=0; tmpf=
tmpf="`mktemp`" && exec 3<>"$tmpf" || st="$?"
rm -f "$tmpf" #remove it even if exec failed; noop if mktemp failed
[ "$st" = 0 ] || exit "$st"
cat >&3
</dev/fd/3 tee "$@" >/dev/null

你可以像這樣使用它:

grep '^[a-zA-Z.:]' "$filepath" \
| sed -r '/^(rm|cd)/d' \
| uniq -u | sponge "$filepath" 

您無法使用簡單的輸出重定向來執行此操作,因為重定向發生在命令啟動之前,並且輸出重定向會截斷輸出檔案。

換句話說,當 grep (管道的第一個簡單命令)啟動時,最後一個重定向已經截斷了輸入/輸出檔案。

據我所知,實際上沒有任何標準 UNIX 實用程式可以進行真正的就地編輯。sed -i僅使用臨時檔案來模擬它。我猜原因是,如果管道步驟失敗,真正的就地過濾很容易損壞檔案。

至於下面發生的事情——兩者|<()使用系統管道,一次通過 IO 一個緩衝區。該機制不會建立臨時檔案(無論如何都不是真正的(檔案系統)檔案),它會嘗試避免一次將整個輸入保存在記憶體中。

答案2

如果你想從同一個文件輸入和輸出,你可以嘗試海綿。正如其描述所述:

sponge reads standard input and writes it out to the specified file. 
Unlike a shell redirect, sponge soaks up all its input before writing 
the output file. This allows constructing pipelines that read from and 
write to the same file.

所以你可以有類似的sed '...' file | grep '...' | sponge [-a] file輸入文件並輸出到相同的文件


另一方面,使用臨時檔案也是使用相同檔案輸入和輸出的好方法。您可以如下初始化暫存檔案:

tempfile=`mktemp tempFile.XXXX` # You can replace "tempFile" with any name you want

這會在運行該腳本的目錄中建立一個名為“tempFile”的臨時文件,擴展名為“XXXX”,其中 x 替換為當前進程號和隨機字母的組合(例如 tempFile.AVm7)。

現在您可以修改管道(或任何管道命令),如下所示:

grep '^[a-zA-Z.:]' "$filepath" \
    | sed -r '/^(rm|cd)/d' \
    | uniq -u \
    > "$tempfile"

過濾後,您可以將臨時文件移至原始文件,如下所示:

mv "$tempfile" "$filepath"

這將消除您的臨時文件,並且您仍保留過濾後的原始文件。但是,有時,您最終可能會創建大量可能不需要且尚未銷毀的臨時文件,因此如果不再需要它們,最好在腳本結束後刪除所有臨時文件來清理目錄。您可以為此編寫一個例程,如下所示:

remove_temp_files() {
    rm `find . -name "tempFile.????"`
}

remove_temp_files然後,您可以簡單地在腳本末尾呼叫例程,從而消除以上述格式建立的任何和所有臨時檔案。

答案3

使用此處文件命令替換在這種情況下,標準方法是:

grep '^[a-zA-Z.:]' <<IN \
    | sed -r '/^(rm|cd)/d' \
    | uniq -u \
    > "$filepath"
$(cat -- "$filepath")
IN

對於其他問題,之前很多問題都有解釋:

相關內容