วนลูปผ่านผลลัพธ์ `ls 'ในสคริปต์เชลล์ bash


93

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

ฉันวางแผนที่จะทำls -1d */เพื่อรับรายชื่อไดเรกทอรี

คำตอบ:


109

แก้ไขไม่ให้ใช้ ls โดยที่ glob จะทำอย่างไรตามที่ @ shawn-j-goff และคนอื่น ๆ แนะนำ

เพียงใช้for..do..doneลูป:

for f in *; do
  echo "File -> $f"
done

คุณสามารถแทนที่*ด้วย*.txtหรือ glob อื่น ๆ ที่ส่งกลับรายการ (ของไฟล์ไดเรกทอรีหรือสิ่งใด ๆ สำหรับเรื่องนั้น) คำสั่งที่สร้างรายการเช่น$(cat filelist.txt)หรือแทนที่จริงด้วยรายการ

ภายในdoลูปคุณเพียงอ้างถึงตัวแปรลูปที่มีคำนำหน้าเครื่องหมายดอลลาร์ (ดังนั้น$fในตัวอย่างด้านบน) คุณสามารถechoทำได้หรือทำสิ่งอื่นที่คุณต้องการ

ตัวอย่างเช่นหากต้องการเปลี่ยนชื่อ.xmlไฟล์ทั้งหมดในไดเรกทอรีปัจจุบันเป็น.txt:

for x in *.xml; do 
  t=$(echo $x | sed 's/\.xml$/.txt/'); 
  mv $x $t && echo "moved $x -> $t"
done

หรือดียิ่งขึ้นถ้าคุณใช้ Bash คุณสามารถใช้การขยายพารามิเตอร์ Bash แทนการวางไข่ subshell:

for x in *.xml; do 
  t=${x%.xml}.txt
  mv $x $t && echo "moved $x -> $t"
done

22
เกิดอะไรขึ้นถ้าชื่อไฟล์มีช่องว่างในนั้น
Daniel A. White

4
น่าเสียดายที่ดาเนียลกล่าวว่ารหัสในคำตอบนี้จะแตกหากไฟล์หรือโฟลเดอร์ใด ๆ มีช่องว่างหรือขึ้นบรรทัดใหม่ในชื่อ มันแสดงให้เห็นในทางที่ผิดที่พบบ่อยมากของลูปและอันตรายโดยทั่วไปของการพยายามที่จะแยกการส่งออกของfor ls@ DanielA ขาวคุณอาจพิจารณาที่จะยอมรับคำตอบนี้ถ้ามันไม่เป็นประโยชน์ (หรืออาจทำให้เข้าใจผิด) เนื่องจากอย่างที่คุณพูดคุณกำลังทำหน้าที่ในไดเรกทอรี คำตอบของ Shawn J. Goff ควรให้วิธีการแก้ปัญหาที่มีประสิทธิภาพและทำงานได้ดีขึ้น
slhck

4
-∞ คุณไม่ควรแยกlsเอาท์พุท , คุณไม่ควรอ่านเอาท์พุทในforห่วงคุณควรใช้$()แทน `` และใช้คำพูดมากขึ้น™ ฉันแน่ใจว่า @CoverosGene มีความหมายดี แต่นี่แย่มาก
l0b0

1
ทางเลือกที่ดีกว่าถ้าคุณต้องการที่จะใช้เป็นls ls -1 | while read line; do stuff; doneอย่างน้อยก็ไม่มีทางทำลายช่องว่างได้
Emmanuel Joubaud

1
กับmv -vคุณไม่จำเป็นต้องใช้echo "moved $x -> $t"
DmitrySandalov

68

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

มีสองวิธีที่ดีมากในการวนซ้ำไฟล์ ที่นี่ฉันใช้echoเพื่อแสดงให้เห็นถึงการทำอะไรกับชื่อไฟล์ คุณสามารถใช้อะไรก็ได้

อย่างแรกคือการใช้คุณสมบัติการหมุนรอบตัวของเชลล์

for dir in */; do
  echo "$dir"
done

เชลล์ขยายออก*/เป็นอาร์กิวเมนต์แยกต่างหากที่forลูปอ่าน แม้ว่าจะมีช่องว่างขึ้นบรรทัดใหม่หรืออักขระแปลก ๆ อื่น ๆ ในชื่อไฟล์forจะเห็นชื่อที่สมบูรณ์แต่ละชื่อเป็นหน่วยอะตอม ไม่ได้แยกวิเคราะห์รายการ แต่อย่างใด

ถ้าคุณต้องการเรียกซ้ำไปยังไดเรกทอรีย่อยการทำเช่นนี้จะไม่เกิดขึ้นถ้าเชลล์ของคุณมีคุณสมบัติการขยายแบบบางอย่าง (เช่นbash's globstarหากเชลล์ของคุณไม่มีคุณสมบัติเหล่านี้หรือหากคุณต้องการให้แน่ใจว่าสคริปต์ของคุณจะทำงาน findบนความหลากหลายของระบบแล้วตัวเลือกต่อไปคือการใช้

find . -type d -exec echo '{}' \;

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

ไวยากรณ์ของ-execข้อโต้แย้งดูตลกเล็กน้อย findรับอาร์กิวเมนต์แรกหลังจาก-execและถือว่าเป็นโปรแกรมที่จะเรียกใช้และทุกอาร์กิวเมนต์ที่ตามมาจะใช้เป็นอาร์กิวเมนต์เพื่อส่งผ่านไปยังโปรแกรมนั้น มีข้อโต้แย้งพิเศษสองข้อที่-execต้องดู คนแรกคือ{}; อาร์กิวเมนต์นี้ถูกแทนที่ด้วยชื่อไฟล์ที่ส่วนก่อนหน้าของการfindสร้าง สิ่งที่สองคือ;สิ่งที่ทำให้เราfindรู้ว่านี่คือจุดสิ้นสุดของรายการอาร์กิวเมนต์ที่ส่งผ่านไปยังโปรแกรม findต้องการสิ่งนี้เพราะคุณสามารถดำเนินการต่อกับอาร์กิวเมนต์ที่มีไว้สำหรับfindและไม่ได้มีไว้สำหรับโปรแกรม exec เหตุผลสำหรับการ\นี้ก็คือเชลล์ก็ปฏิบัติต่อเช่นกัน;พิเศษ - มันแสดงให้เห็นถึงจุดสิ้นสุดของคำสั่งดังนั้นเราจำเป็นต้องหลบหนีเพื่อให้เปลือกให้มันเป็นอาร์กิวเมนต์findมากกว่าที่จะใช้มันสำหรับตัวเอง; อีกวิธีหนึ่งในการทำให้เชลล์ไม่ปฏิบัติกับมันเป็นพิเศษคือใส่ไว้ในเครื่องหมายคำพูด: ';'ใช้ได้ทั้ง\;เพื่อจุดประสงค์นี้


2
+1 นี่เป็นวิธีการที่แน่นอนเมื่อคุณต้องการสร้างรายการไฟล์และใช้มันในคำสั่ง find -exec ถูก จำกัด โดยความสามารถในการรันคำสั่งเดียวเท่านั้น ด้วยการวนซ้ำคุณสามารถไปที่เนื้อหาหัวใจของคุณ
MaQleod

+1 findฉันหวังว่าผู้คนมากขึ้นจะใช้ มีเวทย์มนตร์ที่สามารถทำได้และแม้แต่ข้อ จำกัด การรับรู้ของ-execสามารถแก้ไขได้ ตัวเลือกยังเป็นที่มีคุณค่าสำหรับการใช้งานกับ-print0 xargs
ghoti

ตัวเลือกการวนซ้ำจะไม่แสดงไดเรกทอรีที่ซ่อนอยู่ ตัวเลือกการค้นหาจะไม่แสดงลิงก์
jinawee

15

สำหรับไฟล์ที่มีช่องว่างในคุณจะต้องตรวจสอบให้แน่ใจว่าได้อ้างอิงตัวแปรดังนี้:

 for i in $(ls); do echo "$i"; done; 

หรือคุณสามารถเปลี่ยนตัวแปรสภาพแวดล้อม input field separator (IFS):

 IFS=$'\n';for file in $(ls); do echo $i; done

ในที่สุดขึ้นอยู่กับสิ่งที่คุณทำคุณอาจไม่จำเป็นต้อง ls:

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

ใช้ subshell เป็นอย่างดีในตัวอย่างแรก
Jeremy L

ทำไม IFS ต้องใช้ $ หลังจากการมอบหมายและก่อนอักขระใหม่
Andy Ibanez

3
ชื่อไฟล์สามารถมีการขึ้นบรรทัดใหม่ การแตกบน\nไม่เพียงพอ lsมันไม่เป็นความคิดที่ดีที่จะแนะนำให้แยกการส่งออกของ
ghoti

5

หากคุณติดตั้งGNU Parallel http://www.gnu.org/software/parallel/คุณสามารถทำสิ่งนี้ได้:

ls | parallel echo {} is in this dir

ในการเปลี่ยนชื่อ. txt ทั้งหมดเป็น. xml:

ls *.txt | parallel mv {} {.}.xml

ดูวิดีโอแนะนำสำหรับ GNU Parallel เพื่อเรียนรู้เพิ่มเติม: http://www.youtube.com/watch?v=OpaiGYxkSuQ


4

เพียงเพิ่มคำตอบของ CoverosGene นี่คือวิธีในการแสดงรายการชื่อไดเรกทอรีเท่านั้น:

for f in */; do
  echo "Directory -> $f"
done

1

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

(ในการทดสอบฉันมีปัญหาในการตั้งค่า IFS \nแต่ตั้งค่าเป็น newline backspace ทำงานตามที่แนะนำที่นี่)

เช่น (สมมติว่าlsรูปแบบการค้นหาที่ต้องการส่งผ่าน$1):

SAVEIFS=$IFS
IFS=$(echo -en "\n\b")

FILES=($(/bin/ls "$1"))

for AFILE in ${FILES[@]}
do
    ... do something with a file ...
done

IFS=$SAVEIFS

นี้จะมีประโยชน์โดยเฉพาะอย่างยิ่งใน OS X เช่นจะจับรายชื่อของไฟล์เรียงตามวันที่สร้าง (ที่เก่าแก่ที่สุดไปหาใหม่) ซึ่งเป็นคำสั่งlsls -t -U -r


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

วิธีที่เชื่อถือได้เพียงวิธีเดียวในการถ่ายโอนรายชื่อไฟล์คือการแยกพวกเขาด้วยอักขระ NUL เนื่องจากเป็นวิธีเดียวที่ไม่มีอยู่ในพา ธ ของไฟล์
glglgl

-3

นี่คือวิธีที่ฉันทำ แต่อาจมีวิธีที่มีประสิทธิภาพมากกว่า

ls > filelist.txt

while read filename; do echo filename: "$filename"; done < filelist.txt

6
ติดกับท่อแทนที่ไฟล์:>ls | while read i; do echo filename: $i; done
Jeremy L

เย็น. ฉันควรจะบอกว่าคุณยังสามารถใช้ $ EDITOR filelist.txt ระหว่างคำสั่งสองคำ มีหลายสิ่งที่คุณสามารถทำได้ในโปรแกรมแก้ไขที่ง่ายกว่าในบรรทัดคำสั่ง ไม่เกี่ยวข้องกับคำถามนี้ แต่
TREE

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