วิธีแยกเอาต์พุตคำสั่งกับแต่ละบรรทัด


12
list=`ls -a R*`
echo $list

ภายในเชลล์สคริปต์คำสั่ง echo นี้จะแสดงรายการไฟล์ทั้งหมดจากไดเรกทอรีปัจจุบันเริ่มต้นด้วย R แต่ในบรรทัดเดียว ฉันจะพิมพ์แต่ละรายการในหนึ่งบรรทัดได้อย่างไร

ฉันต้องมีคำสั่งทั่วไปสำหรับทุกสถานการณ์ที่เกิดขึ้นกับls, du, find -type -dฯลฯ


6
หมายเหตุทั่วไป: ไม่ทำเช่นนี้กับ LS, มันจะทำลายในชื่อไฟล์แปลก ดูที่นี่
terdon

คำตอบ:


12

หากเอาต์พุตของคำสั่งมีหลายบรรทัดให้อ้างอิงตัวแปรของคุณเพื่อรักษาบรรทัดใหม่เหล่านั้นเมื่อสะท้อน:

echo "$list"

มิฉะนั้นเชลล์จะขยายและแยกเนื้อหาตัวแปรในช่องว่าง (ช่องว่าง, การขึ้นบรรทัดใหม่, แท็บ) และสิ่งเหล่านั้นจะหายไป


15

แทนการใส่lsเอาท์พุทในตัวแปรอย่างไม่เหมาะสมและจากนั้นechoจึงใช้งานซึ่งจะเป็นการลบสีทั้งหมด

ls -a1

จาก man ls

       -1     list one file per line.  Avoid '\n' with -q or -b

ฉันไม่แนะนำให้คุณทำอะไรกับผลลัพธ์ของlsยกเว้นแสดง :)

ใช้ตัวอย่างเช่นเปลือก globs และforห่วงที่จะทำอะไรกับไฟล์ ...

shopt -s dotglob                  # to include hidden files*

for i in R/*; do echo "$i"; done

* นี้จะไม่รวมถึงไดเรกทอรีปัจจุบัน.หรือผู้ปกครองของตน..แม้ว่า


1
ฉันขอแนะนำให้แทนที่: echo "$i"ด้วย printf "%s\n" "$i" (อันนั้นจะไม่ทำให้หายใจไม่ออกถ้าชื่อไฟล์เริ่มต้นด้วย "-") อันที่จริงส่วนใหญ่เวลาที่ควรจะถูกแทนที่ด้วยecho something printf "%s\n" "something"
Olivier Dulac

2
@OlivierDulac พวกเขาทั้งหมดเริ่มต้นRที่นี่ แต่โดยทั่วไปแล้วใช่ ถึงกระนั้นสำหรับการเขียนสคริปต์การพยายามที่จะรองรับการนำหน้า-ในพา ธ มักทำให้เกิดปัญหา: ผู้คนคิดว่า--มีเพียงบางคำสั่งเท่านั้นที่สนับสนุนบ่งบอกถึงตัวเลือกสุดท้าย ผมเคยเห็นfind . [tests] -exec [cmd] -- {} \;ที่ไม่สนับสนุน[cmd] --ทุกเส้นทางมีเริ่มต้นด้วย.ต่อไป! แต่ที่โต้แย้งกับแทนที่ไม่มีด้วยecho printf '%s\n'printf '%s\n' R/*ที่นี่ใครสามารถแทนที่ห่วงทั้งหมดที่มี Bash นั้นprintfเป็นแบบ builtin ดังนั้นจึงไม่มีข้อ จำกัด เกี่ยวกับจำนวน / ความยาวของการขัดแย้ง
Eliah Kagan

12

ในขณะที่ใส่ไว้ในเครื่องหมายคำพูดตามที่ @muru แนะนำจะทำในสิ่งที่คุณขอแน่นอนคุณอาจต้องการพิจารณาใช้อาร์เรย์สำหรับสิ่งนี้ ตัวอย่างเช่น:

IFS=$'\n' dirs=( $(find . -type d) )

การIFS=$'\n'ทุบตีบอกให้แยกเอาท์พุทบนอักขระขึ้นบรรทัดใหม่ o รับแต่ละองค์ประกอบของอาร์เรย์ หากไม่มีมันก็จะแบ่งช่องว่างดังนั้นa file name with spaces.txtจะเป็น 5 องค์ประกอบที่แยกต่างหากแทนหนึ่ง วิธีการนี้จะแตกถ้าชื่อไฟล์ / ไดเรกทอรีของคุณสามารถมีบรรทัดใหม่ ( \n) มันจะบันทึกแต่ละบรรทัดของผลลัพธ์ของคำสั่งเป็นองค์ประกอบของอาร์เรย์

โปรดทราบว่าฉันยังเปลี่ยนสไตล์เก่า`command`ไป$(command)ซึ่งเป็นไวยากรณ์ที่แนะนำ

ตอนนี้คุณมีอาร์เรย์ที่เรียกว่า$dirsแต่ละส่วนขององค์ประกอบที่เป็นบรรทัดของผลลัพธ์ของคำสั่งก่อนหน้า ตัวอย่างเช่น:

$ find . -type d
.
./olad
./ho
./ha
./ads
./bar
./ga
./da
$ IFS=$'\n' dirs=( $(find . -type d) )
$ for d in "${dirs[@]}"; do
    echo "DIR: $d"
  done
DIR: .
DIR: ./olad
DIR: ./ho
DIR: ./ha
DIR: ./ads
DIR: ./bar
DIR: ./ga
DIR: ./da

ตอนนี้เนื่องจากความไม่พอใจของ bash (ดูที่นี่ ) คุณจะต้องรีเซ็ตIFSกลับไปเป็นค่าดั้งเดิมหลังจากทำเช่นนี้ ดังนั้นให้บันทึกและกำหนดใหม่:

oldIFS="$IFS"
IFS=$'\n' dirs=( $(find . -type d) ) 
IFS="$oldIFS"

หรือทำเอง:

IFS=" "$'\t\n '

หรือเพียงแค่ปิดเทอร์มินัลปัจจุบัน ใหม่ของคุณจะมี IFS ดั้งเดิมตั้งอีกครั้ง


ฉันอาจจะแนะนำสิ่งนี้ แต่ส่วนที่เกี่ยวกับduทำให้มันดู OP มีความสนใจในการรักษารูปแบบผลลัพธ์
muru

ฉันจะทำให้มันทำงานได้อย่างไรถ้าชื่อไดเรกทอรีมีช่องว่าง?
ของหวาน

@ ขนมหวานอ้าว! ควรทำงานกับช่องว่าง (แต่ไม่ใช่บรรทัดใหม่) ในขณะนี้ ขอบคุณที่ชี้นำ
terdon

1
โปรดทราบว่าคุณควรรีเซ็ตหรือยกเลิกการตั้งค่า IFS หลังจากการกำหนดอาร์เรย์นั้น unix.stackexchange.com/q/264635/70524
muru

@muru โอ้ว้าวขอบคุณมากฉันลืมเรื่องแปลก ๆ ไปแล้ว
terdon

2

ถ้าคุณต้องเก็บเอาท์พุทและคุณต้องการอาเรmapfileย์

ก่อนอื่นให้พิจารณาว่าคุณต้องการจัดเก็บเอาต์พุตของคำสั่งของคุณหรือไม่ หากคุณไม่ต้องการให้รันคำสั่ง

หากคุณตัดสินใจว่าคุณต้องการอ่านเอาต์พุตของคำสั่งเป็นอาร์เรย์ของบรรทัดมันเป็นความจริงที่วิธีหนึ่งที่จะทำได้คือปิดการใช้งาน globbing ตั้งค่าIFSให้แยกบรรทัดใช้การทดแทนคำสั่งภายใน( )ไวยากรณ์การสร้างอาร์เรย์และรีเซ็ตIFSภายหลัง ซึ่งซับซ้อนกว่าที่คิด คำตอบของ terdonครอบคลุมบางส่วนของวิธีการนั้น แต่ฉันขอแนะนำให้คุณใช้คำสั่งในตัวmapfileของ Bash shell สำหรับอ่านข้อความเป็นอาร์เรย์ของบรรทัดแทน นี่เป็นกรณีง่ายๆที่คุณอ่านจากไฟล์:

mapfile < filename

MAPFILEนี้อ่านบรรทัดลงในอาร์เรย์ที่เรียกว่า โดยไม่ต้องเปลี่ยนเส้นทางการป้อนข้อมูล ที่คุณจะได้รับการอ่านจากเปลือกเข้ามาตรฐาน (ปกติ terminal ของคุณ) แทนของแฟ้มการตั้งชื่อโดย หากต้องการอ่านอาเรย์อื่นนอกเหนือจากค่าเริ่มต้นให้ส่งชื่อของมัน ตัวอย่างเช่นสิ่งนี้อ่านในอาร์เรย์:< filenamefilenameMAPFILElines

mapfile lines < filename

พฤติกรรมเริ่มต้นอื่นที่คุณอาจเลือกที่จะเปลี่ยนแปลงคืออักขระบรรทัดใหม่ที่ท้ายบรรทัดจะถูกแทนที่ มันจะปรากฏเป็นอักขระตัวสุดท้ายในแต่ละองค์ประกอบอาร์เรย์ (เว้นแต่ว่าอินพุตจะไม่มีอักขระขึ้นบรรทัดใหม่ซึ่งในกรณีนั้นองค์ประกอบสุดท้ายจะไม่มีหนึ่ง) การชอมป์บรรทัดใหม่เหล่านี้เพื่อให้พวกเขาไม่ได้ปรากฏในอาร์เรย์ผ่านตัวเลือกในการ-t mapfileตัวอย่างเช่นสิ่งนี้อ่านไปยังอาร์เรย์recordsและไม่เขียนอักขระบรรทัดใหม่ต่อท้ายไปยังองค์ประกอบอาร์เรย์:

mapfile -t records < filename

นอกจากนี้คุณยังสามารถใช้-tโดยไม่ต้องผ่านชื่ออาร์เรย์ นั่นคือมันใช้งานได้กับชื่ออาเรย์โดยนัยMAPFILEเช่นกัน

mapfileเปลือก builtin สนับสนุนตัวเลือกอื่น ๆ readarrayและมันสามารถผลัดกันจะเรียกว่าเป็น เรียกใช้help mapfile(และhelp readarray) เพื่อดูรายละเอียด

แต่คุณไม่ต้องการอ่านจากไฟล์คุณต้องการอ่านจากเอาต์พุตของคำสั่ง เพื่อให้บรรลุว่าใช้ทดแทนกระบวนการ คำสั่งนี้อ่านบรรทัดจากคำสั่งsome-commandด้วยarguments...เป็นอาร์กิวเมนต์บรรทัดคำสั่งและวางไว้ในmapfileอาเรย์เริ่มต้นMAPFILEของพวกเขาด้วยการยกเลิกตัวอักษรขึ้นบรรทัดใหม่:

mapfile -t < <(some-command arguments...)

การทดแทนกระบวนการแทนที่ด้วยชื่อไฟล์จริงที่สามารถอ่านเอาต์พุตของการทำงานได้ ไฟล์เป็นไพพ์ที่มีชื่อแทนที่จะเป็นไฟล์ปกติและใน Ubuntu มันจะมีชื่อว่า like (บางครั้งจะมีหมายเลขอื่นที่ไม่ใช่) แต่คุณไม่จำเป็นต้องกังวลเกี่ยวกับรายละเอียดเพราะเชลล์จะดูแลมัน เบื้องหลังทั้งหมด<(some-command arguments...)some-command arguments.../dev/fd/6363

คุณอาจคิดว่าคุณสามารถละเลยการเปลี่ยนตัวกระบวนการโดยใช้แทน แต่ที่จะไม่ทำงานเพราะเมื่อคุณมีท่อของคำสั่งหลาย ๆ แยกจากกันโดยทุบตีรันรันคำสั่งทั้งหมดในsubshells ดังนั้นทั้งสองและทำงานในสภาพแวดล้อมของตัวเองเริ่มต้นจาก แต่แยกจากสภาพแวดล้อมของเชลล์ที่คุณใช้ไปป์ไลน์ ใน subshell ที่ทำงานอาร์เรย์จะได้รับการเติมข้อมูล แต่จากนั้นอาร์เรย์นั้นจะถูกยกเลิกเมื่อคำสั่งสิ้นสุดลง ไม่เคยถูกสร้างหรือแก้ไขสำหรับผู้โทรsome-command arguments... | mapfile -t|some-command arguments...mapfile -tmapfile -tMAPFILEMAPFILE

นี่คือสิ่งที่ตัวอย่างในterdon คำตอบของรูปลักษณ์ที่ชอบในสิ่งทั้งปวงถ้าคุณใช้mapfile:

mapfile -t dirs < <(find . -type d)
for d in "${dirs[@]}"; do
    echo "DIR: $d"
done

แค่นั้นแหละ. คุณไม่จำเป็นต้องตรวจสอบว่าIFSมีการตั้งค่าติดตามว่าไม่ได้มีการตั้งค่าไว้และมีค่าใดให้ตั้งเป็นบรรทัดใหม่จากนั้นรีเซ็ตหรือยกเลิกการตั้งค่าใหม่ในภายหลัง คุณไม่จำเป็นต้องปิดการใช้งานglobbing (ตัวอย่างเช่นด้วยset -f) - ซึ่งมีความจำเป็นจริงๆถ้าคุณจะใช้วิธีการอย่างจริงจังตั้งแต่ชื่อไฟล์อาจมี*, ?และ[--then เปิดใช้งานได้ ( set +f) หลังจากนั้น

คุณยังสามารถแทนที่ลูปนั้นด้วยprintfคำสั่งเดียวทั้งหมด- แม้ว่านี่จะไม่เป็นประโยชน์จริง ๆmapfileเพราะคุณสามารถทำได้ไม่ว่าคุณจะใช้mapfileหรือวิธีอื่นในการเติมอาเรย์ นี่เป็นรุ่นที่สั้นกว่า:

mapfile -t < <(find . -type d)
printf 'DIR: %s\n' "${MAPFILE[@]}"

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

ไม่มีวิธีแก้ปัญหาที่เหมาะกับทุกขนาดจริงๆ

คุณขอให้ "คำสั่งทั่วไปสำหรับทุกสถานการณ์" และวิธีการใช้mapfileวิธีการที่ค่อนข้างเป็นเป้าหมาย แต่ฉันขอให้คุณพิจารณาความต้องการของคุณอีกครั้ง

งานที่แสดงด้านบนสามารถทำได้ดีกว่าด้วยfindคำสั่งเดียว:

find . -type d -printf 'DIR: %p\n'

คุณสามารถใช้คำสั่งภายนอกเช่นsedเพิ่มDIR: ไปยังจุดเริ่มต้นของแต่ละบรรทัด นี่เป็นเนื้อหาที่ค่อนข้างน่าเกลียดและไม่เหมือนกับคำสั่ง find ที่จะเพิ่มคำนำหน้า "พิเศษ" ในชื่อไฟล์ที่มีการขึ้นบรรทัดใหม่ แต่มันทำงานโดยไม่คำนึงถึงอินพุตดังนั้นมันจึงเป็นไปตามความต้องการของคุณและมันก็ยังดีกว่า ตัวแปรหรืออาร์เรย์ :

find . -type d | sed 's/^/DIR: /'

หากคุณต้องการแสดงรายการและดำเนินการกับแต่ละไดเรกทอรีที่พบเช่นการเรียกใช้some-commandและผ่านเส้นทางไดเรกทอรีเป็นอาร์กิวเมนต์findให้คุณทำเช่นนั้น:

find . -type d -print -exec some-command {} \;

เป็นอีกตัวอย่างลองกลับไปที่งานทั่วไปของการเพิ่มคำนำหน้าให้แต่ละบรรทัด สมมติว่าฉันต้องการที่จะเห็นผลลัพธ์ของhelp mapfileแต่จำนวนบรรทัด ฉันจะไม่ใช้mapfileสิ่งนี้หรือวิธีอื่นใดที่อ่านมันลงในตัวแปรเชลล์หรืออาเรย์เชลล์ หากhelp mapfile | cat -nว่าไม่ได้จัดรูปแบบที่ฉันต้องการฉันสามารถใช้awk:

help mapfile | awk '{ printf "%3d: %s\n", NR, $0 }'

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

ทางเลือกที่ใช้กันทั่วไปและบางครั้งทำถูกต้องคือการอ่านอินพุตบรรทัดต่อบรรทัดด้วยread -rการวนซ้ำ mapfileถ้าคุณไม่จำเป็นต้องเก็บสายก่อนหน้านี้ในขณะที่ปฏิบัติการในบรรทัดต่อมาและคุณจะต้องใช้การป้อนข้อมูลนานแล้วมันอาจจะดีกว่า แต่มันเกินไปควรหลีกเลี่ยงในกรณีเมื่อคุณสามารถเพียงแค่ท่อกับคำสั่งที่สามารถทำผลงานซึ่งเป็นกรณีส่วนใหญ่

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