cat ไฟล์จำนวนมากเข้าด้วยกันตามลำดับที่ถูกต้อง


23

ฉันมีประมาณ 15,000 ไฟล์ที่มีชื่อfile_1.pdb, file_2.pdbฯลฯ ฉันสามารถแมวประมาณไม่กี่พันเหล่านี้ในการสั่งซื้อโดยการทำ:

cat file_{1..2000}.pdb >> file_all.pdb

อย่างไรก็ตามหากฉันทำสิ่งนี้กับไฟล์ 15,000 ไฟล์ฉันจะได้รับข้อผิดพลาด

-bash: /bin/cat: Argument list too long

ฉันได้เห็นปัญหานี้ได้รับการแก้ไขโดยการทำfind . -name xx -exec xxแต่จะไม่รักษาลำดับที่ไฟล์เข้าร่วม ฉันจะบรรลุสิ่งนี้ได้อย่างไร


3
ไฟล์ที่สิบชื่ออะไร (หรือไฟล์ใด ๆ ที่มีลำดับเลขหลักเดียวมากกว่า)
roaima

ฉัน (ตอนนี้) มี 15,000 ไฟล์ในไดเรกทอรีและcat file_{1..15000}.pdbโครงสร้างของคุณทำงานได้ดีสำหรับฉัน
roaima

11
ขึ้นอยู่กับระบบว่าข้อ จำกัด คืออะไร getconf ARG_MAXควรบอก
ilkkachu

3
ลองเปลี่ยนคำถามของคุณเป็น "พันไฟล์" หรือ "ไฟล์จำนวนมาก" อาจทำให้คำถามค้นหาได้ง่ายขึ้นสำหรับคนอื่นที่มีปัญหาคล้ายกัน
msouth

คำตอบ:


49

การใช้find, sortและxargs:

find . -maxdepth 1 -type f -name 'file_*.pdb' -print0 |
sort -zV |
xargs -0 cat >all.pdb

findคำสั่งพบไฟล์ที่เกี่ยวข้องทั้งหมดแล้วพิมพ์ pathnames พวกเขาออกไปsortที่ไม่ "ฉบับเรียง" เพื่อให้พวกเขาในการสั่งซื้อที่เหมาะสม (ถ้าตัวเลขในชื่อไฟล์ที่ได้รับเป็นศูนย์ที่เต็มไปด้วยความกว้างคงที่เราจะไม่ได้จำเป็น-V) xargsใช้รายการชื่อพา ธ ที่เรียงลำดับแล้วและเรียกcatใช้ในชื่อกลุ่มเหล่านี้ให้มากที่สุด

สิ่งนี้จะใช้ได้แม้ว่าชื่อไฟล์จะมีอักขระแปลกเช่นบรรทัดใหม่และช่องว่าง เราใช้-print0ด้วยfindที่จะให้sortNUL สิ้นสุดชื่อในการเรียงลำดับและจับใช้เหล่านี้ sort อ่านชื่อที่ถูกยกเลิกด้วย nul ด้วยเช่นกัน-zxargs-0

file_*.pdbโปรดทราบว่าผมเขียนผลไปยังไฟล์ที่มีชื่อไม่ตรงกับรูปแบบที่


วิธีการแก้ปัญหาข้างต้นใช้แฟลกที่ไม่ได้มาตรฐานบางตัวสำหรับยูทิลิตี้บางตัว สิ่งเหล่านี้ได้รับการสนับสนุนโดยการนำ GNU ไปใช้งานของยูทิลิตี้เหล่านี้และอย่างน้อยก็โดย OpenBSD และการใช้งาน macOS

ธงที่ไม่ได้มาตรฐานที่ใช้คือ

  • -maxdepth 1เพื่อสร้างfindเฉพาะไดเรกทอรีอันดับสูงสุด แต่ไม่มีไดเรกทอรีย่อย POSIXly ใช้find . ! -name . -prune ...
  • -print0เพื่อสร้างfindชื่อพา ธ ที่ถูกยกเลิกด้วย Nul (ซึ่งถือว่าเป็น POSIX แต่ถูกปฏิเสธ) หนึ่งสามารถใช้-exec printf '%s\0' {} +แทน
  • -zเพื่อสร้างsortบันทึกที่ถูกยกเลิก ไม่มีการเทียบเท่า POSIX
  • -Vเพื่อให้sortการจัดเรียงเช่นหลัง200 3ไม่มีการเทียบเท่า POSIX แต่สามารถแทนที่ด้วยการเรียงลำดับตัวเลขในส่วนเฉพาะของชื่อไฟล์หากชื่อไฟล์มีคำนำหน้าคงที่
  • -0เพื่อสร้างการxargsอ่านที่ถูกยกเลิก nul ไม่มีการเทียบเท่า POSIX POSIXly xargsหนึ่งจะต้องพูดชื่อไฟล์ในรูปแบบที่ได้รับการยอมรับจาก

หาก pathnames ที่มีความประพฤติดีและถ้าโครงสร้างไดเรกทอรีแบน (ไม่มีไดเรกทอรีย่อย) แล้วใครจะทำอะไรโดยไม่ต้องสถานะเหล่านี้ยกเว้นด้วย-Vsort


1
คุณไม่จำเป็นต้องมีการยกเลิกค่า null ที่ไม่เป็นมาตรฐานสำหรับสิ่งนี้ ชื่อไฟล์เหล่านี้น่าเบื่ออย่างยิ่งและเครื่องมือ POSIX สามารถจัดการได้อย่างสมบูรณ์
เควิน

6
นอกจากนี้คุณยังสามารถเขียนมากกว่านี้ชัดถ้อยชัดคำกับข้อกำหนดถามในฐานะที่เป็นหรือแม้กระทั่งที่มีจุดของเควินprintf ‘file_%d.pdb\0’ {1..15000} | xargs -0 cat วิธีecho file_{1..15000}.pdb | xargs catการfindแก้ปัญหามีค่าใช้จ่ายมากขึ้นเนื่องจากต้องค้นหาระบบไฟล์สำหรับไฟล์เหล่านั้น แต่จะมีประโยชน์มากกว่าเมื่อไฟล์บางไฟล์ไม่มีอยู่
kojiro

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

1
@chrylis การเปลี่ยนเส้นทางไม่ได้เป็นส่วนหนึ่งของอาร์กิวเมนต์ของคำสั่งและxargsไม่ใช่การcatเปลี่ยนเส้นทาง ( catการร้องขอแต่ละครั้งจะใช้xargsเอาต์พุตมาตรฐาน) หากเราได้พูดไปxargs -0 sh -c 'cat >all.pdb'แล้วมันก็สมเหตุสมผลที่จะใช้>>แทน>ถ้านั่นคือสิ่งที่คุณกำลังบอกใบ้
Kusalananda

1
ดูเหมือนว่าsort -n -k1.6จะใช้งานได้ (สำหรับชื่อเดิมfile_nnnชื่อไฟล์หรือsort -n -k1.5ชื่อที่ไม่มีเครื่องหมายขีดเส้นใต้)
สกอตต์

14

ด้วยzsh( {1..15000}ตัวดำเนินการมาจากไหน):

autoload zargs # best in ~/.zshrc
zargs file_{1..15000}.pdb -- cat > file_all.pdb

หรือสำหรับfile_<digits>.pdbไฟล์ทั้งหมดตามลำดับตัวเลข:

zargs file_<->.pdb(n) -- cat > file_all.pdb

(ซึ่ง<x-y>เป็นตัวดำเนินการแบบกลมที่ตรงกับตัวเลขทศนิยม x ถึง y โดยที่ไม่มีxหรือyมันคือเลขทศนิยมใด ๆ เทียบเท่ากับextendedglob's [0-9]##หรือkshglob' s +([0-9])(หนึ่งหรือมากกว่าหนึ่งหลัก))

ด้วยการksh93ใช้catคำสั่งbuiltin (ดังนั้นจึงไม่ได้รับผลกระทบจากข้อ จำกัด ของการexecve()เรียกระบบเนื่องจากไม่มีการประมวลผล ):

command /opt/ast/bin/cat file_{1..15000}.pdb > file_all.pdb

ด้วยbash/ zsh/ ksh93(ซึ่งสนับสนุนzsh's {x..y}และมีprintfbuiltin):

printf '%s\n' file_{1..15000}.pdb | xargs cat > file_all.pdb

ในระบบ GNU หรือเทียบเท่าคุณสามารถใช้seq:

seq -f 'file_%.17g.pdb' 15000 | xargs cat > file_all.pdb

สำหรับxargsโซลูชันที่ใช้งานต้องใช้ความระมัดระวังเป็นพิเศษสำหรับชื่อไฟล์ที่มีช่องว่างเครื่องหมายคำพูดเดี่ยวหรือคู่หรือเครื่องหมายแบ็กสแลช

กดไลค์ให้-It's a trickier filename - 12.pdbใช้:

seq -f "\"./-It's a trickier filename - %.17g.pdb\"" 15000 |
  xargs cat > file_all.pdb

นี่seq -f | xarg cat > คือทางออกที่หรูหราและมีประสิทธิภาพที่สุด (IMHO)
Hastur

ตรวจสอบชื่อไฟล์ที่ซับซ้อน ... อาจจะ '"./-It'\''s a trickier filename - %.17g.pdb"'?
Hastur

@Hastur อ๊ะ! ใช่ขอบคุณฉันเปลี่ยนเป็นไวยากรณ์ทางเลือก คุณจะทำงานเช่นกัน
Stéphane Chazelas

11

ห่วงสำหรับเป็นไปได้และง่ายมาก

for i in file_{1..15000}.pdb; do cat $i >> file_all.pdb; done

ข้อเสียคือคุณเรียกcatนรกหลายครั้ง แต่ถ้าคุณจำไม่ได้ว่าทำอย่างไรกับสิ่งนั้นfindและค่าใช้จ่ายในการขอร้องก็ไม่เลวร้ายในสถานการณ์ของคุณ


ฉันมักจะเพิ่ม a echo $i;ในร่างกายลูปเป็น "ตัวบ่งชี้ความคืบหน้า"
Rolf

3
seq 1 15000 | awk '{print "file_"$0".dat"}' | xargs cat > file_all.pdb

1
awk สามารถทำงานของ seq ได้ที่นี่และ seq สามารถทำงานของ awk seq -f file_%.10g.pdb 15000ได้ โปรดทราบว่าseqไม่ใช่คำสั่งมาตรฐาน
Stéphane Chazelas

ขอบคุณStéphane - ฉันคิดว่า seq -f เป็นวิธีที่ดีในการทำเช่นนี้; จะจำไว้ว่า
LarryC

2

หลักฐาน

คุณไม่ควรเกิดขึ้นจากข้อผิดพลาดที่สำหรับเพียง 15k ไฟล์ที่มีรูปแบบเฉพาะชื่อที่[ 1 , 2 ]

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

โซลูชันเรียกใช้คำสั่งจากไดเรกทอรีนั้น

(cd That/Directory ; cat file_{1..2000}.pdb >> file_all.pdb )

ทางออกที่ดีที่สุดถ้าฉันเดาไม่ดีและคุณเรียกใช้จากไดเรกทอรีที่เป็นไฟล์ ...
IMHO ทางออกที่ดีที่สุดคือStéphane Chazelas ' ;

seq -f 'file_%.17g.pdb' 15000 | xargs cat > file_all.pdb

ด้วย printf หรือ seq; ทดสอบกับไฟล์ 15k ที่มีเฉพาะหมายเลขภายในแคชล่วงหน้าเท่านั้นแม้จะเป็นไฟล์ที่เร็วกว่า (ปัจจุบันและยกเว้นไฟล์ OP หนึ่งไฟล์จากไดเรกทอรีเดียวกันกับไฟล์ที่มีอยู่)

บางคำเพิ่มเติม

คุณควรส่งผ่านไปยังบรรทัดคำสั่งเชลล์ของคุณได้นานขึ้น
บรรทัดคำสั่งของคุณคือ 213914 ตัวอักษรและมีความยาว 1,5003 คำ
cat file_{1..15000}.pdb " > file_all.pdb" | wc

... แม้แต่การเพิ่ม 8 ไบต์สำหรับแต่ละคำคือ 333 938 ไบต์ (0.3M) ด้านล่างจาก 2097142 (2.1M) ที่รายงานโดยARG_MAXเคอร์เนล 3.13.0 หรือ 2088232 ที่มีขนาดเล็กกว่าเล็กน้อยรายงานว่า"ความยาวสูงสุดของคำสั่งที่เราสามารถทำได้จริง ใช้ "โดยxargs --show-limits

ให้มันดูในระบบของคุณเพื่อการส่งออกของ

getconf ARG_MAX
xargs --show-limits

แนวทางแก้ปัญหาความขี้เกียจ

ในกรณีเช่นนี้ฉันชอบที่จะทำงานกับบล็อกเพราะมักจะเป็นทางออกที่มีประสิทธิภาพเวลา
ตรรกะ (ถ้ามี) คือฉันขี้เกียจเขียน 1 ... 1,000 1001..2000 ฯลฯ ฯลฯ ...
ดังนั้นฉันจึงขอให้สคริปต์ทำเพื่อฉัน
หลังจากฉันตรวจสอบผลลัพธ์แล้วความถูกต้องฉันเปลี่ยนเส้นทางไปยังสคริปต์

... แต่ความเกียจคร้านเป็นสภาวะของจิตใจ
เนื่องจากฉันแพ้xargs(ฉันควรใช้xargsที่นี่จริง ๆ) และฉันไม่ต้องการตรวจสอบวิธีการใช้ฉันจึงเสร็จสิ้นการคิดค้นล้อให้ตรงเวลาตามตัวอย่างด้านล่าง (tl; dr)

โปรดทราบว่าเนื่องจากชื่อไฟล์ถูกควบคุม (ไม่มีช่องว่าง, บรรทัดใหม่ ... ) คุณสามารถไปได้อย่างง่ายดายด้วยบางสิ่งบางอย่างเช่นสคริปต์ด้านล่าง

TL; DR

เวอร์ชัน 1: ส่งผ่านเป็นพารามิเตอร์ทางเลือกหมายเลขไฟล์ที่ 1 สุดท้ายขนาดบล็อกไฟล์เอาต์พุต

#!/bin/bash
StartN=${1:-1}          # First file number
EndN=${2:-15000}        # Last file number
BlockN=${3:-100}        # files in a Block 
OutFile=${4:-"all.pdb"} # Output file name

CurrentStart=$StartN 
for i in $(seq $StartN $BlockN $EndN)
do 
  CurrentEnd=$i ;  
    cat $(seq -f file_%.17g.pdb $CurrentStart $CurrentEnd)  >> $OutFile;
  CurrentStart=$(( CurrentEnd + 1 )) 
done
# Here you may need to do a last iteration for the part cut from seq
[[ $EndN -ge $CurrentStart ]] && 
    cat $(seq -f file_%.17g.pdb $CurrentStart $EndN)  >> $OutFile;

เวอร์ชัน 2

โทรทุบตีเพื่อขยาย (ช้าลงเล็กน้อยในการทดสอบของฉัน ~ 20%)

#!/bin/bash
StartN=${1:-1}          # First file number
EndN=${2:-15000}        # Last file number
BlockN=${3:-100}        # files in a Block 
OutFile=${4:-"all.pdb"} # Output file name

CurrentStart=$StartN 
for i in $(seq $StartN $BlockN $EndN)
do 
  CurrentEnd=$i ;
    echo  cat file_{$CurrentStart..$CurrentEnd}.pdb | /bin/bash  >> $OutFile;
  CurrentStart=$(( CurrentEnd + 1 )) 
done
# Here you may need to do a last iteration for the part cut from seq
[[ $EndN -ge $CurrentStart ]] && 
    echo  cat file_{$CurrentStart..$EndN}.pdb | /bin/bash  >> $OutFile;

แน่นอนคุณสามารถไปข้างหน้าและกำจัดseq [ 3 ] (จาก coreutils) และทำงานโดยตรงกับตัวแปรใน bash หรือใช้ python หรือคอมไพล์โปรแกรม ac เพื่อทำมัน[ 4 ] ...


โปรดทราบว่าสั้นสำหรับ%g %.6gมันจะแทน 1,000,000 เป็น 1e + 06 เช่น
Stéphane Chazelas

คนขี้เกียจจริงๆใช้เครื่องมือที่ออกแบบมาสำหรับงานของการทำงานรอบที่ E2BIG ข้อ จำกัด เช่นxargs, zsh ของzargsหรือ'sksh93 command -x
Stéphane Chazelas

seqไม่ใช่ทุบตีในตัวมันเป็นคำสั่งจาก GNU coreutils seq -f %g 1000000 1000000เอาท์พุต 1e + 06 แม้ใน coreutils เวอร์ชันล่าสุด
Stéphane Chazelas

@ StéphaneChazelas Laziness เป็นสภาวะของจิตใจ แปลกที่จะพูด แต่ฉันรู้สึกอบอุ่นขึ้นเมื่อฉันเห็น (และตรวจสอบผลลัพธ์ของคำสั่งที่เป็นอนุกรม) และจากนั้นเปลี่ยนเส้นทางไปยังการดำเนินการเท่านั้น การก่อสร้างนั้นให้ฉันคิดน้อยกว่าxarg... แต่ฉันเข้าใจว่ามันเป็นเรื่องส่วนตัวและอาจเกี่ยวข้องกับฉันเท่านั้น
Hastur

@ StéphaneChazelas Gotcha ใช่แล้ว ... แก้ไขแล้ว ขอบคุณ ฉันทดสอบเฉพาะไฟล์ 15k ที่กำหนดโดย OP เท่านั้นแย่มาก
Hastur

0

อีกวิธีที่จะทำได้

(cat file_{1..499}.pdb; cat file_{500..999}.pdb; cat file_{1000..1499}.pdb; cat file_{1500..2000}.pdb) >> file_all.pdb
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.