มีคำสั่ง bash ซึ่งนับจำนวนไฟล์ที่ตรงกับรูปแบบหรือไม่?
ตัวอย่างเช่นฉันต้องการรับจำนวนไฟล์ทั้งหมดในไดเรกทอรีที่ตรงกับรูปแบบนี้: log*
มีคำสั่ง bash ซึ่งนับจำนวนไฟล์ที่ตรงกับรูปแบบหรือไม่?
ตัวอย่างเช่นฉันต้องการรับจำนวนไฟล์ทั้งหมดในไดเรกทอรีที่ตรงกับรูปแบบนี้: log*
คำตอบ:
ซับหนึ่งแบบง่ายนี้ควรใช้กับเชลล์ใด ๆ ไม่ใช่แค่ทุบตี:
ls -1q log* | wc -l
ls -1q จะให้หนึ่งบรรทัดต่อไฟล์แม้ว่าจะมีช่องว่างหรืออักขระพิเศษเช่นการขึ้นบรรทัดใหม่
เอาต์พุตถูกไพพ์ไปที่ wc -l ซึ่งนับจำนวนบรรทัด
ls
เพราะมันสร้างกระบวนการลูก log*
ถูกขยายโดยเชลล์ไม่ใช่ls
ดังนั้นecho
จะทำง่าย
logs
ไดเรกทอรีดังกล่าวเนื้อหาของไดเรกทอรีบันทึกนั้นจะถูกนับด้วย สิ่งนี้อาจไม่ได้ตั้งใจ
คุณสามารถทำได้อย่างปลอดภัย (เช่นจะไม่ถูกบั๊กโดยไฟล์ที่มีช่องว่างหรือ\n
ในชื่อ) ด้วย bash:
$ shopt -s nullglob
$ logfiles=(*.log)
$ echo ${#logfiles[@]}
คุณต้องเปิดใช้งานnullglob
เพื่อที่คุณจะไม่ได้รับตัวอักษร*.log
ใน$logfiles
อาร์เรย์หากไม่มีไฟล์ที่ตรงกัน (ดูวิธีการ "เลิกทำ" a 'set -x'สำหรับตัวอย่างวิธีการรีเซ็ตอย่างปลอดภัย)
shopt -u nullglob
ควรข้ามขั้นสุดท้ายหากnullglob
ไม่ได้รับการยกเลิกจากนั้นคุณก็เริ่ม
*.log
ด้วย Just *
จะนับไดเรกทอรี หากไฟล์ที่คุณต้องการให้ระบุมีการตั้งชื่อแบบดั้งเดิมของการใช้งานname.extension
*.*
มีคำตอบมากมายที่นี่ แต่บางคำตอบไม่ได้คำนึงถึง
-l
)*.log
แทนlog*
logs
ว่าตรงlog*
)นี่คือทางออกที่จัดการกับพวกเขาทั้งหมด:
ls 2>/dev/null -Ubad1 -- log* | wc -l
คำอธิบาย:
-U
สาเหตุls
ที่ไม่เรียงลำดับรายการหมายความว่าไม่จำเป็นต้องโหลดรายชื่อไดเรกทอรีทั้งหมดในหน่วยความจำ-b
พิมพ์หนีแบบ C สำหรับตัวอักษร nongraphic \n
ขับเคลื่อนก่อให้เกิดการขึ้นบรรทัดใหม่ที่จะพิมพ์เป็น-a
พิมพ์ไฟล์ทั้งหมดแม้ไฟล์ที่ซ่อนอยู่ (ไม่จำเป็นอย่างเคร่งครัดเมื่อ glob log*
หมายถึงไม่มีไฟล์ที่ซ่อนอยู่)-d
พิมพ์ไดเร็กทอรีโดยไม่พยายามแสดงรายการเนื้อหาของไดเร็กทอรีซึ่งเป็นสิ่งที่ls
ปกติจะทำ-1
ตรวจสอบให้แน่ใจว่าอยู่ในหนึ่งคอลัมน์ (ls ทำสิ่งนี้โดยอัตโนมัติเมื่อเขียนไปยังไพพ์ดังนั้นจึงไม่จำเป็นต้องเคร่งครัด)2>/dev/null
เปลี่ยนเส้นทาง stderr เพื่อให้มีไฟล์บันทึก 0 ไฟล์ให้เพิกเฉยต่อข้อความแสดงข้อผิดพลาด (โปรดทราบว่าshopt -s nullglob
จะทำให้ls
รายการไดเรกทอรีทำงานทั้งหมดแทน)wc -l
กินรายชื่อไดเรกทอรีขณะที่มันถูกสร้างขึ้นดังนั้นการส่งออกของls
ไม่เคยอยู่ในหน่วยความจำ ณ จุดใดก็ได้--
ชื่อไฟล์จะถูกแยกออกจากคำสั่งโดยใช้--
เพื่อไม่ให้เข้าใจว่าเป็นอาร์กิวเมนต์ls
(ในกรณีที่log*
ถูกลบ)เชลล์จะขยายlog*
ไปยังรายการไฟล์ทั้งหมดซึ่งอาจทำให้หน่วยความจำหมดหากมีไฟล์จำนวนมากดังนั้นการรันผ่าน grep จะดีกว่า:
ls -Uba1 | grep ^log | wc -l
อันสุดท้ายนี้จัดการไดเรกทอรีของไฟล์ที่มีขนาดใหญ่มากโดยไม่ต้องใช้หน่วยความจำจำนวนมาก (แม้ว่ามันจะใช้ subshell) -d
ไม่จำเป็นเพราะมันเป็นเพียงรายการเนื้อหาของไดเรกทอรีปัจจุบัน
สำหรับการค้นหาแบบเรียกซ้ำ:
find . -type f -name '*.log' -printf x | wc -c
wc -c
จะนับจำนวนตัวอักษรในผลลัพธ์find
ขณะที่-printf x
บอกfind
ให้พิมพ์ทีx
ละรายการ
สำหรับการค้นหาแบบไม่เรียกซ้ำให้ทำดังนี้:
find . -maxdepth 1 -type f -name '*.log' -printf x | wc -c
-name '*.log'
มันจะนับไฟล์ทั้งหมดซึ่งเป็นสิ่งที่ฉันต้องการสำหรับกรณีการใช้ของฉัน นอกจากนี้ธง -maxdepth ยังมีประโยชน์อย่างมากขอบคุณ!
find
; เพียงพิมพ์อย่างอื่นนอกเหนือจากชื่อไฟล์คำต่อคำ
คำตอบที่ยอมรับได้สำหรับคำถามนี้ผิด แต่ฉันมีตัวแทนน้อยจึงไม่สามารถเพิ่มความคิดเห็นได้
Mat ได้รับคำตอบที่ถูกต้องสำหรับคำถามนี้:
shopt -s nullglob
logfiles=(*.log)
echo ${#logfiles[@]}
ปัญหาของคำตอบที่ได้รับการยอมรับคือ wc -l จะนับจำนวนอักขระขึ้นบรรทัดใหม่และนับถึงแม้ว่าพวกเขาจะพิมพ์ไปยังเทอร์มินัลเป็น '?' ในเอาต์พุตของ 'ls -l' ซึ่งหมายความว่าคำตอบที่ยอมรับล้มเหลวเมื่อชื่อไฟล์มีอักขระขึ้นบรรทัดใหม่ ฉันได้ทดสอบคำสั่งที่แนะนำ:
ls -l log* | wc -l
และรายงานค่า 2 อย่างไม่ถูกต้องแม้ว่าจะมีเพียง 1 ไฟล์ที่ตรงกับรูปแบบที่ชื่อเกิดขึ้นมีอักขระขึ้นบรรทัดใหม่ ตัวอย่างเช่น:
touch log$'\n'def
ls log* -l | wc -l
หากคุณมีไฟล์จำนวนมากและคุณไม่ต้องการใช้shopt -s nullglob
โซลูชันอาร์เรย์ที่หรูหราและทุบตีคุณสามารถใช้การค้นหาและอื่น ๆ ตราบใดที่คุณไม่พิมพ์ชื่อไฟล์ (ซึ่งอาจมีการขึ้นบรรทัดใหม่)
find -maxdepth 1 -name "log*" -not -name ".*" -printf '%i\n' | wc -l
นี่จะค้นหาไฟล์ทั้งหมดที่ตรงกับบันทึก * และไม่เริ่มต้นด้วย .*
- "ไม่ใช่ชื่อ. *" เป็นค่าใหม่ แต่สิ่งสำคัญที่ควรทราบคือค่าเริ่มต้นสำหรับ "ls" คือการไม่แสดงจุดไฟล์ แต่เป็นค่าเริ่มต้น สำหรับการค้นหาคือการรวมพวกเขา
นี่เป็นคำตอบที่ถูกต้องและจัดการกับชื่อไฟล์ประเภทใดก็ได้ที่คุณสามารถใส่ได้เพราะชื่อไฟล์จะไม่ผ่านระหว่างคำสั่ง
แต่shopt nullglob
คำตอบคือคำตอบที่ดีที่สุด!
find
vs การใช้ls
เป็นวิธีที่แตกต่างกันสองวิธีในการแก้ปัญหา find
ไม่ได้แสดงอยู่บนเครื่องเสมอไป แต่ls
มักจะเป็น
find
อาจไม่มีตัวเลือกแฟนซีทั้งหมดสำหรับls
ทั้ง
-maxdepth 1
find
ทำสิ่งนี้ตามค่าเริ่มต้น สิ่งนี้สามารถสร้างความสับสนได้หากไม่มีใครรู้ว่ามีโฟลเดอร์ลูกที่ซ่อนอยู่และอาจทำให้เกิดประโยชน์ในการใช้ls
ในบางสถานการณ์ซึ่งจะไม่รายงานไฟล์ที่ซ่อนอยู่ตามค่าเริ่มต้น
นี่คือหนึ่งซับของฉันสำหรับเรื่องนี้
file_count=$( shopt -s nullglob ; set -- $directory_to_search_inside/* ; echo $#)
set --
จะไม่ทำอะไรเลยนอกจากเตรียมเราให้พร้อม$#
ที่จะเก็บจำนวนของอาร์กิวเมนต์บรรทัดคำสั่งที่ส่งผ่านไปยังโปรแกรมเชลล์
คุณสามารถใช้อ็อพชัน -R เพื่อค้นหาไฟล์พร้อมกับไฟล์ที่อยู่ในไดเร็กทอรี recursive
ls -R | wc -l // to find all the files
ls -R | grep log | wc -l // to find the files which contains the word log
คุณสามารถใช้รูปแบบบน grep
(ชื่อเสียงไม่เพียงพอที่จะแสดงความคิดเห็น)
นี่คือBUGGY :
ls -1q some_pattern | wc -l
หากshopt -s nullglob
เกิดขึ้นจะตั้งจะพิมพ์จำนวนทั้งหมดไฟล์ปกติไม่ได้เป็นเพียงคนที่มีรูปแบบ (การทดสอบบน CentOS-8 และ Cygwin) ใครจะรู้ว่าบั๊กที่ไร้ความหมายอื่นทำอะไรls
มีใครบ้าง
นี่คือที่ถูกต้องและรวดเร็วยิ่งขึ้น:
shopt -s nullglob; files=(some_pattern); echo ${#files[@]};
มันทำงานได้ตามที่คาดหวัง
0.006
บน CentOS และ0.083
Cygwin (ในกรณีที่ใช้อย่างระมัดระวัง)
0.000
บน CentOS และ0.003
Cygwin
คุณสามารถกำหนดคำสั่งดังกล่าวได้อย่างง่ายดายโดยใช้ฟังก์ชันเชลล์ วิธีนี้ไม่จำเป็นต้องใช้โปรแกรมภายนอกและไม่วางไข่กระบวนการลูกใด ๆ มันไม่ได้พยายามls
แยกวิเคราะห์ที่เป็นอันตรายและจัดการตัวอักษร "พิเศษ" (ช่องว่าง, การขึ้นบรรทัดใหม่, แบ็กสแลชและอื่น ๆ ) ได้ดี ขึ้นอยู่กับกลไกการขยายชื่อไฟล์ที่เชลล์จัดเตรียมไว้ให้เท่านั้น มันเข้ากันได้กับอย่างน้อย sh, bash และ zsh
บรรทัดด้านล่างกำหนดฟังก์ชั่นที่เรียกว่าcount
พิมพ์จำนวนของการขัดแย้งที่มันถูกเรียกว่า
count() { echo $#; }
เพียงเรียกมันด้วยรูปแบบที่ต้องการ:
count log*
เพื่อให้ผลลัพธ์ถูกต้องเมื่อรูปแบบการวนรอบไม่ตรงกันตัวเลือกเชลล์nullglob
(หรือfailglob
- ซึ่งเป็นพฤติกรรมเริ่มต้นบน zsh) จะต้องตั้งค่าเมื่อการขยายเวลาเกิดขึ้น มันสามารถตั้งค่าเช่นนี้:
shopt -s nullglob # for sh / bash
setopt nullglob # for zsh
dotglob
ทั้งนี้ขึ้นอยู่กับสิ่งที่คุณต้องการที่จะนับที่คุณอาจจะสนใจในตัวเลือกเปลือก
น่าเสียดายที่มีการทุบตีอย่างน้อยก็ไม่ใช่เรื่องง่ายที่จะตั้งค่าตัวเลือกเหล่านี้ในเครื่อง หากคุณไม่ต้องการตั้งค่าไว้ทั่วโลกโซลูชันที่ตรงไปตรงมาที่สุดคือการใช้ฟังก์ชันในลักษณะที่ซับซ้อนกว่านี้:
( shopt -s nullglob ; shopt -u failglob ; count log* )
หากคุณต้องการกู้คืนไวยากรณ์ที่มีน้ำหนักเบาcount log*
หรือหากคุณต้องการหลีกเลี่ยงการวางไข่ subshell จริงๆคุณอาจแฮ็คบางสิ่งตามบรรทัดต่อไปนี้:
# sh / bash:
# the alias is expanded before the globbing pattern, so we
# can set required options before the globbing gets expanded,
# and restore them afterwards.
count() {
eval "$_count_saved_shopts"
unset _count_saved_shopts
echo $#
}
alias count='
_count_saved_shopts="$(shopt -p nullglob failglob)"
shopt -s nullglob
shopt -u failglob
count'
เป็นโบนัสฟังก์ชั่นนี้เป็นการใช้งานทั่วไปที่มากกว่า ตัวอย่างเช่น
count a* b* # count files which match either a* or b*
count $(jobs -ps) # count stopped jobs (sh / bash)
โดยการเปลี่ยนฟังก์ชั่นเป็นไฟล์สคริปต์ (หรือโปรแกรม C ที่เทียบเท่า) สามารถเรียกได้จาก PATH มันยังสามารถประกอบกับโปรแกรมเช่นfind
และxargs
:
find "$FIND_OPTIONS" -exec count {} \+ # count results of a search
ฉันได้รับคำตอบนี้เป็นจำนวนมากของความคิดโดยเฉพาะอย่างยิ่งได้รับสิ่งที่ห้ามแยก-LS ตอนแรกฉันลองแล้ว
<คำเตือน! ไม่ทำงาน>
du --inodes --files0-from=<(find . -maxdepth 1 -type f -print0) | awk '{sum+=int($1)}END{print sum}'
</ คำเตือน! ไม่ทำงาน>
ซึ่งทำงานหากมีเพียงชื่อไฟล์เช่น
touch $'w\nlf.aa'
แต่ล้มเหลวถ้าฉันสร้างชื่อไฟล์แบบนี้
touch $'firstline\n3 and some other\n1\n2\texciting\n86stuff.jpg'
ในที่สุดฉันก็เกิดสิ่งที่ฉันวางไว้ด้านล่าง หมายเหตุฉันพยายามรับจำนวนไฟล์ทั้งหมดในไดเรกทอรี (ไม่รวมไดเรกทอรีย่อย) ฉันคิดว่ามันพร้อมกับคำตอบโดย @Mat และ @Dan_Yard รวมถึงข้อกำหนดอย่างน้อยที่สุดที่กำหนดโดย @mogsie (ฉันไม่แน่ใจเกี่ยวกับความทรงจำ) ฉันคิดว่าคำตอบของ @mogsie นั้นถูกต้อง แต่ฉันพยายามหลีกเลี่ยงการแยกวิเคราะห์อยู่เสมอls
เว้นแต่จะเป็นสถานการณ์ที่เฉพาะเจาะจงอย่างยิ่ง
awk -F"\0" '{print NF-1}' < <(find . -maxdepth 1 -type f -print0) | awk '{sum+=$1}END{print sum}'
อ่านง่ายขึ้น:
awk -F"\0" '{print NF-1}' < \
<(find . -maxdepth 1 -type f -print0) | \
awk '{sum+=$1}END{print sum}'
นี่เป็นการค้นหาไฟล์โดยเฉพาะการลบเอาต์พุตด้วยอักขระ null (เพื่อหลีกเลี่ยงปัญหาเกี่ยวกับช่องว่างและ linefeeds) จากนั้นนับจำนวนอักขระ null จำนวนไฟล์จะน้อยกว่าหนึ่งตัวอักษรเป็นโมฆะเนื่องจากจะมีตัวอักษรเป็นโมฆะในตอนท้าย
เพื่อตอบคำถามของ OP มีสองกรณีที่ต้องพิจารณา
1) การค้นหาแบบไม่เรียกซ้ำ:
awk -F"\0" '{print NF-1}' < \
<(find . -maxdepth 1 -type f -name "log*" -print0) | \
awk '{sum+=$1}END{print sum}'
2) การค้นหาแบบเรียกซ้ำ โปรดทราบว่าสิ่งที่อยู่ภายใน-name
พารามิเตอร์อาจต้องมีการเปลี่ยนแปลงเพื่อการทำงานที่แตกต่างกันเล็กน้อย (ไฟล์ที่ซ่อนเป็นต้น)
awk -F"\0" '{print NF-1}' < \
<(find . -type f -name "log*" -print0) | \
awk '{sum+=$1}END{print sum}'
หากใครต้องการที่จะแสดงความคิดเห็นเกี่ยวกับวิธีที่คำตอบเหล่านี้เปรียบเทียบกับคำตอบที่ฉันได้กล่าวไว้ในนี้โปรดทำ
หมายเหตุฉันไปถึงกระบวนการคิดนี้ในขณะที่รับคำตอบนี้
นี่คือสิ่งที่ฉันทำเสมอ:
ls log * | awk 'END {print NR}'
awk 'END{print NR}'
wc -l
ควรจะเทียบเท่ากับ
ls -1 log* | wc -l
ซึ่งหมายความว่ารายการหนึ่งไฟล์ต่อบรรทัดแล้วไปป์มันไปที่คำสั่งการนับคำด้วยการเปลี่ยนพารามิเตอร์เพื่อนับบรรทัด
หากต้องการนับทุกอย่างเพียงไปป์ ls กับ word count line:
ls | wc -l
หากต้องการนับด้วยรูปแบบให้ไปที่ grep ก่อน:
ls | grep log | wc -l
-l
เนื่องจากต้องใช้stat(2)
กับแต่ละไฟล์และเพื่อวัตถุประสงค์ในการนับไม่เพิ่มอะไรเลย