簡體   English   中英

替換找到的第一個文件中的字符串

[英]Replace string in the first file it is found

我有一堆這樣命名的文件:

chapter1.tex
chapter2.tex
chapter3.tex
...
chapter 10.tex
chapter 11.tex
etc.

我正在嘗試使用sed在所有文件中查找並用ZZZZZZ替換AAAAAA的第一個實例。

sed -i "0,/AAAAAA/s//ZZZZZZ/" chapter*.tex

我試過這個上面的命令,但有兩個問題:

  1. 它查找並替換每個文件中AAAAAA的第一個實例。 我只想要所有文件中的第一個實例。
  2. 我懷疑,像許多 Bash 工具一樣,它沒有按順序正確地對我的文件進行排序。 例如,如果我輸入ls那么chapter10.tex會在chapter1.tex之前列出。 按章節順序搜索文件至關重要。

如何使用 Bash 工具從一大堆文件中查找和替換第一個實例,所以只替換第一個找到的文件中的第一個實例,同時也尊重文件順序( chapter1.tex是第一個, chapter10.tex是第十)?

這是一個基於 bash 循環的解決方案,它可以處理諸如chapter 10.tex文件名,即帶空格的文件名等:

while IFS= read -r -d '' file; do
   if grep -q 'AAAAAA' "$file"; then
      echo "changing $file"
      sed -i '0,/AAAAAA/s//ZZZZZZ/' "$file"
      break
   fi
done < <(printf '%s\0' chapter*.tex | sort -z -V)

這是假設sedsort都來自 gnu utils。


如果你有支持就地編輯的 gnu awk 4+ 版本,即-i inplace那么你可以用單個awk替換grep + sed

while IFS= read -r -d '' file; do
   awk -i inplace '!n {n=sub(/AAAAAA/, "ZZZZZZ")} 1;
   END {exit !n}' "$file" && break
done < <(printf '%s\0' chapter*.tex | sort -z -V)

使用完整的 GNU 工具箱,您不需要循環。

printf '%s\0' chapter*.tex    \
| sort -zV                    \
| xargs -0 grep -FlZ 'AAAAAA' \
| head -zn1                   \
| xargs -0r sed -i 's/AAAAAA/ZZZZZZ/'

這可能對您有用(GNU sed 和 grep):

grep -ns 'AAAAAA' chapter{1..9999}.txt | head -1 |
sed -nE 's#([^:]*):([^:]*):.*#sed -i "\2s/AAAAAA/ZZZZZZ/" \1#e'

使用 grep 和 bash 的大括號擴展來識別一個可能匹配的文件和行號,並構建一個 sed 腳本以在該行號處更新該文件。

NB 大括號擴展以正確的順序生成文件名,grep 的-s命令行選項會抑制不存在的文件消息。


使用 GNU 並行的替代方法:

grep -sno 'AAAAAA' chapter{1..9999}.txt | head -1 |
parallel --colsep : sed '{2}s/{3}/ZZZZZZ/' {1}

#更新

我站在巨人的背上,哈哈

感謝@potong 提供了帶有支架擴展的出色排序解決方案! 這意味着整個事情可以簡化為單進程單行:

sed -i '0,/^AAA/{ /^AAA/{ s/AAA/ZZZ/; h; } }; ${ x; /./{x;q;}; x; }' chapter\ {[0-9],[0-9][0-9]}.tex 

#編輯

正如所指出的,下面的原始解決方案將處理和更改每個文件中的第一次出現,並且不會更正文件順序。 @anubhava 已經提供了一個優秀、優雅的排序解決方案,我不會嘗試改進。

while IFS= read -r -d '' file; do lst+=( "$file" ); done < <(printf '%s\0' chapter*.tex | sort -z -V)

這會以正確的順序創建一個文件名列表,可以將其傳遞給sed的單個調用以集體處理它們。

要將其應用於基於sed的解決方案的排序,並且僅在任何文件中出現第一次出現 -

sed -i '0,/^AAA/{ /^AAA/{ s/AAA/ZZZ/; h; } }; ${ x; /./{x;q;}; x; }' "${lst[@]}"

這將期待通過每個文件並改變它發現該文件中的第一次出現, h掩門在第一次發現它的線。 在每個文件的最后一行用電子郵件x更改為保持緩沖區,檢查是否掉之后存在模式緩沖區任何當前行。 如果沒有,它會將其交換回來並繼續。 如果,它交換回來和q UITS,跳過所有后續文件。

雖然有些復雜,但這不會為每個文件生成進程。


原來的


使用雙重條件 -

sed -i '0,/AAAAAA/{ /AAAAAA/s/AAAAAA/ZZZZZZ/ }' chapter*.tex

要查看相同的一般邏輯,請執行以下操作:

$: cat a.tex b.tex
111
AAA
BBB
AAA
222

111
AAA
BBB
AAA
222

$: sed -i '0,/^AAA/{ /^AAA/s/AAA/ZZZ/; }' *.tex
$: cat a.tex b.tex
111
ZZZ
BBB
AAA
222

111
ZZZ
BBB
AAA
222

'0,/^AAA/是正確的,因為它的范圍從文件的開頭到目標字符串的第一次出現。

{打開一個塊,我們可以在其中使用第二次搜索來確保它只影響目標字符串。

在區塊內部, /^AAA/s/AAA/ZZZ/; 替換 AAA 字符串並忽略它之前的所有記錄。 }關閉塊。 之后的所有記錄都將保持不變。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM