
我試圖弄清楚如何從所有 .txt 檔案中獲取總行數。我認為問題出在第 6 -> 行let $((total = total + count ))
。有人知道這個的正確形式是什麼嗎?
#!/bin/bash
total=0
find /home -type f -name "*.txt" | while read -r FILE; do
count=$(grep -c ^ < "$FILE")
echo "$FILE has $count lines"
let $((total = total + count ))
done
echo TOTAL LINES COUNTED: $total
謝謝
答案1
你的第6行最好寫成
total=$(( total + count ))
……但最好還是使用一個工具製成用於計算行數(假設您想計算換行符,即正確終止的行數)
find . -name '*.txt' -type f -exec cat {} + | wc -l
這會查找當前目錄中或當前目錄下文件名以.txt
.所有這些文件都連接成一個流並通過管道傳輸到wc -l
,輸出總行數,這就是問題的標題和文字所要求的。
完整腳本:
#!/bin/sh
nlines=$( find . -name '*.txt' -type f -exec cat {} + | wc -l )
printf 'Total number of lines: %d\n' "$nlines"
若要同時取得各個檔案的行數,請考慮
find . -name '*.txt' -type f -exec sh -c '
wc -l "$@" |
if [ "$#" -gt 1 ]; then
sed "\$d"
else
cat
fi' sh {} + |
awk '{ tot += $1 } END { printf "Total: %d\n", tot }; 1'
這會wc -l
呼叫批次文件,輸出每個單獨文件的行數。當wc -l
使用多個檔案名稱呼叫時,它將在末尾輸出一行,其中包含總計數。如果使用多個檔案名稱參數呼叫sed
內聯腳本,我們將刪除這一行。sh -c
然後將行計數和檔案路徑名稱的長列表傳遞給awk
,它只是將計數相加(並傳遞資料),並在最後向使用者顯示總計數。
在 GNU 系統上,該wc
工具可以從 nul 分隔流中讀取路徑名。您可以在這些系統上使用它find
及其-print0
操作,如下所示:
find . -name '*.txt' -type f -print0 |
wc --files0-from=- -l
在這裡,找到的路徑名作為空分隔清單通過管道傳遞以wc
使用非標準-print0
.該wc
實用程式與非標準--files0-from
選項一起使用來讀取通過管道傳遞的清單。
答案2
let $((total = total + count ))
這可行,但有點多餘,因為 和 都let
開始$(( .. ))
算術擴展。
let "total = total + count"
、let "total += count"
、: $((total = total + count))
或中的任何一個total=$((total + count))
都可以在不重複的情況下完成此操作。最後兩個應該與標準外殼相容,let
但不是。
total=0
find /home -type f -name "*.txt" | while read -r FILE; do
total=...
done
echo TOTAL LINES COUNTED: $total
您沒有說出您的意思是什麼,但您遇到的一個問題是,在 Bash 中,管道的各個部分默認在子 shell 中運行,因此循環total
內部所做的任何更改while
在循環之後都不可見。看:為什麼我的變數在一個「while read」迴圈中是本地變量,但在另一個看似相似的循環中卻不是?
您可以使用shopt -s lastpipe
讓管道的最後一部分在 shell 中運作;或將while
和分組echo
:
find ... | { while ...
done; echo "$total"; }
當然,find ... | while read -r FILE;
包含換行符號或以空格開頭/結尾的檔案名稱會出現問題。你可以用以下方法解決這個問題
find ... -print0 | while IFS= read -r -d '' FILE; do ...
或者,如果您不關心每個文件行數的細分,並且知道您的文件是完整的文字文件,並且沒有丟失最後的換行符,那麼您可以簡單地將所有文件連接在一起並wc -l
在其上運行。
如果您的文件可能缺少最後一行末尾的換行符,並且您想計算最後一個不完整的行,那麼您不能這樣做,並且需要繼續使用grep -c ^
而不是wc -l
. (計算最後的部分行幾乎是使用grep -c ^
而不是的唯一原因wc -l
。)
看:在文件末尾添加新行有什麼意義?和為什麼文字檔案應該以換行符號結尾?就這樣。
另外,如果您只想要總數,所有與模式相符的檔案都是常規檔案(因此-type f
可以刪除測試),並且您有 Bash 和 GNU grep,您也可以這樣做:
shopt -s globstar
shopt -s dotglob
grep -h -c ^ **/*.txt | awk '{ a += $0 } END { print a }'
**/*.txt
是一個遞歸 glob,需要明確啟用它才能運作。dotglob
使該 glob 也符合以點開頭的檔案名稱。grep -h
抑制輸出中的檔案名,並且awk
腳本計算總和。由於沒有列印文件名,因此即使其中一些有問題,這也應該可以工作。
或者,正如 @fra-san 所建議的,基於另一個現已刪除的答案:
grep -r -c -h --include='*.sh' ^ |awk '{ a+= $0 } END {print a }'
答案3
let total+=count
會起作用,不需要$(( ))
這種形式的算術評估。
但你最好用 來做到這一點wc -l
。
find /home -type f -name '*.txt' -exec wc -l {} +
如果您想要像上面的 shell 腳本一樣自訂輸出,或者如果檔案名稱的數量可能超出 linux 上 bash 的 ~2MB 行長度限制,您可以使用awk
或perl
來進行計數。任何東西都比 shell while-read 循環更好(參見為什麼使用 shell 循環處理文字被認為是不好的做法?)。例如:
find /home -type f -name '*.txt' -exec perl -lne '
$files{$ARGV}++;
END {
foreach (sort keys %files) {
printf "%s has %s lines\n", $_, $files{$_};
$total+=$files{$_}
};
printf "TOTAL LINES COUNTED: %s\n", $total
}' {} +
注意:find ... -exec perl
上面的指令將忽略空文件,而該wc -l
版本將以行數 0 列出它們。
OTOH,它將進行行計數和總計任何檔案數量,即使它們不能全部容納在一個 shell 命令行中 - 版本wc -l
將列印二或更多total
行在這種情況下 - 可能不會發生,但如果發生的話也不是你想要的。
這應該可以工作,它使用wc -l
並將輸出傳輸到 perl 中以將其更改為所需的輸出格式:
$ find /home -type f -name '*.txt' -exec wc -l {} + |
perl -lne 'next if m/^\s+\d+\s+total$/;
s/\s+(\d+)\s+(.*)/$2 has $1 lines/;
print;
$total += $1;
END { print "TOTAL LINES COUNTED: $total"}'
答案4
嘗試這個:
#!/bin/bash
export total=$(find . -name '*.txt' -exec wc -l "{}" ";" | awk 'BEGIN{sum=0} {sum+=$1} END{print sum}')
echo TOTAL LINES COUNTED ${total}