มีคำสั่ง bash ซึ่งนับจำนวนไฟล์หรือไม่


182

มีคำสั่ง bash ซึ่งนับจำนวนไฟล์ที่ตรงกับรูปแบบหรือไม่?

ตัวอย่างเช่นฉันต้องการรับจำนวนไฟล์ทั้งหมดในไดเรกทอรีที่ตรงกับรูปแบบนี้: log*

คำตอบ:


243

ซับหนึ่งแบบง่ายนี้ควรใช้กับเชลล์ใด ๆ ไม่ใช่แค่ทุบตี:

ls -1q log* | wc -l

ls -1q จะให้หนึ่งบรรทัดต่อไฟล์แม้ว่าจะมีช่องว่างหรืออักขระพิเศษเช่นการขึ้นบรรทัดใหม่

เอาต์พุตถูกไพพ์ไปที่ wc -l ซึ่งนับจำนวนบรรทัด


10
ฉันจะไม่ใช้-lเนื่องจากต้องใช้stat(2)กับแต่ละไฟล์และเพื่อวัตถุประสงค์ในการนับไม่เพิ่มอะไรเลย
camh

12
ฉันจะไม่ใช้lsเพราะมันสร้างกระบวนการลูก log*ถูกขยายโดยเชลล์ไม่ใช่lsดังนั้นechoจะทำง่าย
cdarke

2
ยกเว้นเสียงสะท้อนจะไม่ทำงานหากคุณมีชื่อไฟล์ที่มีช่องว่างหรืออักขระพิเศษ
Daniel

4
@WalterTross นั่นเป็นความจริง (ไม่ใช่ว่าประสิทธิภาพนั้นเป็นข้อกำหนดของคำถามต้นฉบับ) ฉันเพิ่งค้นพบว่า -q ดูแลไฟล์ที่มีบรรทัดใหม่แม้เมื่อเอาต์พุตไม่ใช่เทอร์มินัล และการตั้งค่าสถานะเหล่านี้ได้รับการสนับสนุนจากแพลตฟอร์มและเชลล์ทั้งหมดที่ฉันได้ทดสอบ อัปเดตคำตอบขอขอบคุณคุณและ camh สำหรับอินพุต!
แดเนียล

3
หากมีไดเรกทอรีที่เรียกว่าlogsไดเรกทอรีดังกล่าวเนื้อหาของไดเรกทอรีบันทึกนั้นจะถูกนับด้วย สิ่งนี้อาจไม่ได้ตั้งใจ
mogsie

54

คุณสามารถทำได้อย่างปลอดภัย (เช่นจะไม่ถูกบั๊กโดยไฟล์ที่มีช่องว่างหรือ\nในชื่อ) ด้วย bash:

$ shopt -s nullglob
$ logfiles=(*.log)
$ echo ${#logfiles[@]}

คุณต้องเปิดใช้งานnullglobเพื่อที่คุณจะไม่ได้รับตัวอักษร*.logใน$logfiles อาร์เรย์หากไม่มีไฟล์ที่ตรงกัน (ดูวิธีการ "เลิกทำ" a 'set -x'สำหรับตัวอย่างวิธีการรีเซ็ตอย่างปลอดภัย)


2
บางทีชี้ให้เห็นอย่างชัดเจนว่านี่เป็นคำตอบBash- เท่านั้นโดยเฉพาะอย่างยิ่งสำหรับผู้เข้าชมใหม่ที่ยังไม่ได้เร่งความเร็วในความแตกต่างระหว่าง sh และ bash
tripleee

นอกจากนี้shopt -u nullglobควรข้ามขั้นสุดท้ายหากnullglobไม่ได้รับการยกเลิกจากนั้นคุณก็เริ่ม
tripleee

หมายเหตุ: การแทนที่*.logด้วย Just *จะนับไดเรกทอรี หากไฟล์ที่คุณต้องการให้ระบุมีการตั้งชื่อแบบดั้งเดิมของการใช้งานname.extension *.*
AlainD

52

มีคำตอบมากมายที่นี่ แต่บางคำตอบไม่ได้คำนึงถึง

  • ชื่อไฟล์ที่มีช่องว่างบรรทัดใหม่หรืออักขระควบคุมในนั้น
  • ชื่อไฟล์ที่ขึ้นต้นด้วยเครื่องหมายขีดคั่น (ลองนึกภาพไฟล์ที่เรียกว่า-l)
  • ไฟล์ที่ซ่อนที่เริ่มต้นด้วยจุด (ถ้า glob เป็น*.logแทนlog*
  • ไดเรกทอรีที่ตรงกับ glob (เช่นไดเรกทอรีที่เรียกlogsว่าตรงlog*)
  • ไดเรกทอรีว่าง (เช่นผลลัพธ์คือ 0)
  • ไดเรกทอรีที่มีขนาดใหญ่มาก (การแสดงไดเรกทอรีทั้งหมดอาจทำให้หน่วยความจำหมด)

นี่คือทางออกที่จัดการกับพวกเขาทั้งหมด:

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ไม่จำเป็นเพราะมันเป็นเพียงรายการเนื้อหาของไดเรกทอรีปัจจุบัน


48

สำหรับการค้นหาแบบเรียกซ้ำ:

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

6
แม้ว่าคุณจะไม่มีไฟล์ที่มีช่องว่างก็ตามผู้ใช้อื่น ๆ ในสคริปต์ของคุณอาจพบไฟล์ที่มีชื่อประสงค์ร้ายทำให้สคริปต์ล้มเหลว นอกจากนี้คนอื่น ๆ ที่พบสิ่งนี้ใน StackOverflow อาจมีไฟล์ที่มีการขึ้นบรรทัดใหม่และจำเป็นต้องรู้ข้อผิดพลาด
mogsie

FYI ถ้าคุณเพิ่งออกไป-name '*.log'มันจะนับไฟล์ทั้งหมดซึ่งเป็นสิ่งที่ฉันต้องการสำหรับกรณีการใช้ของฉัน นอกจากนี้ธง -maxdepth ยังมีประโยชน์อย่างมากขอบคุณ!
starmandeluxe

2
สิ่งนี้จะสร้างผลลัพธ์ที่ไม่ถูกต้องหากมีชื่อไฟล์ที่ขึ้นบรรทัดใหม่ วิธีแก้ปัญหาง่ายด้วยfind; เพียงพิมพ์อย่างอื่นนอกเหนือจากชื่อไฟล์คำต่อคำ
tripleee

8

คำตอบที่ยอมรับได้สำหรับคำถามนี้ผิด แต่ฉันมีตัวแทนน้อยจึงไม่สามารถเพิ่มความคิดเห็นได้

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

6

หากคุณมีไฟล์จำนวนมากและคุณไม่ต้องการใช้shopt -s nullglobโซลูชันอาร์เรย์ที่หรูหราและทุบตีคุณสามารถใช้การค้นหาและอื่น ๆ ตราบใดที่คุณไม่พิมพ์ชื่อไฟล์ (ซึ่งอาจมีการขึ้นบรรทัดใหม่)

find -maxdepth 1 -name "log*" -not -name ".*" -printf '%i\n' | wc -l

นี่จะค้นหาไฟล์ทั้งหมดที่ตรงกับบันทึก * และไม่เริ่มต้นด้วย .* - "ไม่ใช่ชื่อ. *" เป็นค่าใหม่ แต่สิ่งสำคัญที่ควรทราบคือค่าเริ่มต้นสำหรับ "ls" คือการไม่แสดงจุดไฟล์ แต่เป็นค่าเริ่มต้น สำหรับการค้นหาคือการรวมพวกเขา

นี่เป็นคำตอบที่ถูกต้องและจัดการกับชื่อไฟล์ประเภทใดก็ได้ที่คุณสามารถใส่ได้เพราะชื่อไฟล์จะไม่ผ่านระหว่างคำสั่ง

แต่shopt nullglobคำตอบคือคำตอบที่ดีที่สุด!


คุณควรปรับปรุงคำตอบเดิมแทนที่จะตอบอีกครั้ง
qodeninja

ฉันคิดว่าการใช้findvs การใช้lsเป็นวิธีที่แตกต่างกันสองวิธีในการแก้ปัญหา findไม่ได้แสดงอยู่บนเครื่องเสมอไป แต่lsมักจะเป็น
mogsie

2
แต่แล้วกล่องน้ำมันหมูที่ไม่มีfindอาจไม่มีตัวเลือกแฟนซีทั้งหมดสำหรับlsทั้ง
tripleee

1
ให้สังเกตด้วยว่าสิ่งนี้จะขยายไปยัง-maxdepth 1
แผนผังไดเร

1
หมายเหตุวิธีนี้จะนับไฟล์ภายในไดเรกทอรีที่ซ่อนอยู่ในจำนวน findทำสิ่งนี้ตามค่าเริ่มต้น สิ่งนี้สามารถสร้างความสับสนได้หากไม่มีใครรู้ว่ามีโฟลเดอร์ลูกที่ซ่อนอยู่และอาจทำให้เกิดประโยชน์ในการใช้lsในบางสถานการณ์ซึ่งจะไม่รายงานไฟล์ที่ซ่อนอยู่ตามค่าเริ่มต้น
MrPotato เฮด

6

นี่คือหนึ่งซับของฉันสำหรับเรื่องนี้

 file_count=$( shopt -s nullglob ; set -- $directory_to_search_inside/* ; echo $#)

ฉันใช้ googling เพื่อทำความเข้าใจ แต่มันดีมาก! ดังนั้นset -- จะไม่ทำอะไรเลยนอกจากเตรียมเราให้พร้อม$#ที่จะเก็บจำนวนของอาร์กิวเมนต์บรรทัดคำสั่งที่ส่งผ่านไปยังโปรแกรมเชลล์
xverges

@xverges ใช่ "shopt -s nullglob" สำหรับการไม่นับไฟล์ที่ซ่อนอยู่ (.files) set - ใช้สำหรับเก็บ / ตั้งค่าพารามิเตอร์ตำแหน่ง (จำนวนไฟล์ในกรณีนี้) และ # $ สำหรับแสดงจำนวนพารามิเตอร์ตำแหน่ง (นับจำนวนไฟล์)
Zee

3

คุณสามารถใช้อ็อพชัน -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


3

ความคิดเห็นที่สำคัญ

(ชื่อเสียงไม่เพียงพอที่จะแสดงความคิดเห็น)

นี่คือBUGGY :

ls -1q some_pattern | wc -l

หากshopt -s nullglobเกิดขึ้นจะตั้งจะพิมพ์จำนวนทั้งหมดไฟล์ปกติไม่ได้เป็นเพียงคนที่มีรูปแบบ (การทดสอบบน CentOS-8 และ Cygwin) ใครจะรู้ว่าบั๊กที่ไร้ความหมายอื่นทำอะไรlsมีใครบ้าง

นี่คือที่ถูกต้องและรวดเร็วยิ่งขึ้น:

shopt -s nullglob; files=(some_pattern); echo ${#files[@]};

มันทำงานได้ตามที่คาดหวัง


และเวลาทำงานแตกต่างกัน
อันดับที่ 1: 0.006บน CentOS และ0.083Cygwin (ในกรณีที่ใช้อย่างระมัดระวัง)
ที่ 2: 0.000บน CentOS และ0.003Cygwin


2

คุณสามารถกำหนดคำสั่งดังกล่าวได้อย่างง่ายดายโดยใช้ฟังก์ชันเชลล์ วิธีนี้ไม่จำเป็นต้องใช้โปรแกรมภายนอกและไม่วางไข่กระบวนการลูกใด ๆ มันไม่ได้พยายาม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

2

ฉันได้รับคำตอบนี้เป็นจำนวนมากของความคิดโดยเฉพาะอย่างยิ่งได้รับสิ่งที่ห้ามแยก-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}'

หากใครต้องการที่จะแสดงความคิดเห็นเกี่ยวกับวิธีที่คำตอบเหล่านี้เปรียบเทียบกับคำตอบที่ฉันได้กล่าวไว้ในนี้โปรดทำ


หมายเหตุฉันไปถึงกระบวนการคิดนี้ในขณะที่รับคำตอบนี้



0
ls -1 log* | wc -l

ซึ่งหมายความว่ารายการหนึ่งไฟล์ต่อบรรทัดแล้วไปป์มันไปที่คำสั่งการนับคำด้วยการเปลี่ยนพารามิเตอร์เพื่อนับบรรทัด


ตัวเลือก "-1" ไม่จำเป็นเมื่อทำการไพพ์เอาท์พุท ls แต่คุณอาจต้องการซ่อนข้อความแสดงข้อผิดพลาด ls หากไม่มีไฟล์ที่ตรงกับรูปแบบ ฉันแนะนำ "ls log * 2> / dev / null | wc -l"
JohnMudd

การอภิปรายภายใต้คำตอบของ Daniel ก็มีความเกี่ยวข้องเช่นกัน วิธีนี้ใช้ได้ผลดีเมื่อคุณไม่มีไดเรกทอรีหรือชื่อไฟล์ที่ตรงกับการขึ้นบรรทัดใหม่ แต่อย่างน้อยคำตอบที่ดีควรชี้ให้เห็นเงื่อนไขขอบเขตเหล่านี้และคำตอบที่ดีไม่ควรมี ข้อผิดพลาดหลายอย่างเกิดขึ้นเนื่องจากมีคนคัดลอก / วางโค้ดที่ไม่เข้าใจ ดังนั้นการชี้ให้เห็นข้อบกพร่องอย่างน้อยก็ช่วยให้พวกเขาเข้าใจว่าต้องระวังอะไร (ที่ได้รับข้อบกพร่องอื่น ๆ อีกมากมายเกิดขึ้นเพราะพวกเขาไม่สนใจคำเตือนและแล้วสิ่งที่เปลี่ยนแปลงหลังจากที่พวกเขาคิดว่ารหัสก็มากพอที่อาจจะดีสำหรับวัตถุประสงค์ของพวกเขา.)
tripleee

-1

หากต้องการนับทุกอย่างเพียงไปป์ ls กับ word count line:

ls | wc -l

หากต้องการนับด้วยรูปแบบให้ไปที่ grep ก่อน:

ls | grep log | wc -l
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.