ทำไมคำสั่ง“ ls | ไม่ถึง ไฟล์” ทำงานอย่างไร


32

ฉันได้ศึกษาเกี่ยวกับบรรทัดคำสั่งและเรียนรู้ว่า|(ไปป์ไลน์) นั้นหมายถึงการเปลี่ยนทิศทางเอาต์พุตจากคำสั่งไปยังอินพุตของอีกอันหนึ่ง เหตุใดคำสั่งls | fileจึงไม่ทำงาน

file อินพุตเป็นหนึ่งในชื่อไฟล์เพิ่มเติมเช่น file filename1 filename2

lsเอาท์พุทเป็นรายการของไดเรกทอรีและไฟล์ในโฟลเดอร์ดังนั้นฉันคิดว่าls | fileควรจะแสดงประเภทไฟล์ของทุกไฟล์ในโฟลเดอร์

เมื่อฉันใช้มันอย่างไรก็ตามผลลัพธ์คือ:

    Usage: file [-bcEhikLlNnprsvz0] [--apple] [--mime-encoding] [--mime-type]
        [-e testname] [-F separator] [-f namefile] [-m magicfiles] file ...
    file -C [-m magicfiles]
    file [--help]

เนื่องจากมีข้อผิดพลาดบางอย่างกับการใช้fileคำสั่ง


2
หากคุณกำลังใช้งานธรรมดาlsแสดงว่าคุณต้องการไฟล์ทั้งหมดในไดเรกทอรีปัจจุบันที่จัดการด้วยfileคำสั่ง ... ดังนั้นทำไมไม่ทำอย่างง่ายๆ: file *ซึ่งจะตอบกลับด้วยบรรทัดสำหรับทุกไฟล์โฟลเดอร์
Knud Larsen

file *เป็นวิธีที่ฉลาดที่สุดฉันแค่สงสัยว่าทำไมการใช้งานlsเอาต์พุตจึงไม่ทำงาน ข้อสงสัยเคลียร์ :)
IanC

6
สถานที่ตั้งมีข้อบกพร่อง: "อินพุตไฟล์เป็นอีกหนึ่งชื่อไฟล์เช่นไฟล์ filename1 filename2" นั่นไม่ใช่อินพุต นี่คืออาร์กิวเมนต์บรรทัดคำสั่งเนื่องจาก @John Kugelman ชี้ด้านล่าง
Monty Harder

3
สัมผัส, การแยกวิเคราะห์lsโดยทั่วไปเป็นความคิดที่ดี
kojiro

คำตอบ:


71

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

ความแตกต่างคืออะไร?

  • cmd arg1 arg2 arg3อาร์กิวเมนต์บรรทัดคำสั่งจะถูกเมื่อคุณเขียนธงและชื่อไฟล์หลังคำสั่งในขณะที่ ในเชลล์สคริปต์ขัดแย้งเหล่านี้มีอยู่เป็นตัวแปร$1, $2, $3ฯลฯ ใน C คุณต้องการเข้าถึงได้ผ่านทางchar **argvและข้อโต้แย้งint argcmain()

  • อินพุตมาตรฐาน stdin เป็นสตรีมของข้อมูล บางโปรแกรมชอบcatหรือwcอ่านจาก stdin เมื่อไม่ได้รับอาร์กิวเมนต์บรรทัดคำสั่งใด ๆ ในเชลล์สคริปต์คุณสามารถใช้readเพื่อรับอินพุตบรรทัดเดียว ใน C คุณสามารถใช้scanf()หรือgetchar()ระหว่างตัวเลือกต่าง ๆ

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

คุณสามารถใช้xargsการแปลง stdin ls | xargs fileลงในข้อโต้แย้งในขณะที่ ยังคงเป็นterdon กล่าววจีวิภาคlsเป็นความคิดที่ไม่ดี วิธีที่ตรงที่สุดในการทำเช่นนี้คือ:

file *

2
หรือบังคับที่จะได้รับชื่อไฟล์จากการป้อนข้อมูลโดยใช้file ls | file -f -ยังเป็นความคิดที่เลว
spectras

2
@Braiam> นั่นคือประเด็น และlsเอาท์พุทของท่อนั้นไปfileที่ stdin ของ ลองดู
สเปกตรัม

4
@Braiam> แน่นอนมันสิ้นเปลืองและอันตราย แต่มันใช้งานได้และมันดีถ้าเปรียบเทียบกับตัวเลือกที่ดีกว่าถ้า OP เรียนรู้ที่จะใช้การเปลี่ยนเส้นทาง เพื่อความสมบูรณ์ฉันยังสามารถพูดถึงfile $(ls)ซึ่งยังใช้งานได้ในอีกทางหนึ่ง
สเปกตรัม

2
ฉันคิดว่าหลังจากอ่านคำตอบทั้งหมดฉันมีภาพรวมของปัญหาที่ใหญ่กว่าแม้ว่าฉันคิดว่าฉันจะต้องอ่านเพิ่มเติมเพื่อให้เข้าใจมันทั้งหมด ครั้งแรกที่เห็นได้ชัดโดยใช้ท่อและเปลี่ยนเส้นทางไม่ได้แยกออกเป็นข้อโต้แย้งแต่เป็นSTDIN ซึ่งฉันยังคงต้องอ่านเพิ่มเติมเพื่อทำความเข้าใจให้ดีขึ้น แต่การสร้างอาร์กิวเมนต์การค้นหาผิวเผินดูเหมือนว่าข้อความจะถูกวิเคราะห์ไปยังโปรแกรมในอาร์เรย์และSTDINเป็นวิธีการรวมข้อมูลสำหรับไฟล์หรือผลลัพธ์ (ไม่ใช่โปรแกรมทั้งหมดที่ออกแบบมาเพื่อ ทำงานกับ "รวมกำไร" นี้)
IanC

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

18

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

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

$ ls / | grep etc
etc

ดังนั้นหากต้องการใช้เอาต์พุตของคำสั่งที่แสดงชื่อไฟล์เป็นข้อความ (เช่นlsหรือfind) เป็นอินพุตสำหรับคำสั่งที่ใช้ชื่อไฟล์คุณจำเป็นต้องใช้ลูกเล่นบางอย่าง เครื่องมือทั่วไปสำหรับสิ่งนี้คือxargs:

$ ls
file1 file2

$ ls | xargs wc
 9  9 38 file1
 5  5 20 file2
14 14 58 total

lsที่ผมกล่าวก่อนแม้ว่าคุณจริงๆไม่ต้องการที่จะแยกการส่งออกของ สิ่งที่ต้องการfindจะดีกว่า (คนprint0พิมพ์\0แทน newilne หลังจากชื่อไฟล์แต่ละและ-0การxargsปล่อยให้มันจัดการกับข้อมูลดังกล่าวนี้เป็นเคล็ดลับที่จะทำให้คำสั่งที่ทำงานของคุณด้วยชื่อไฟล์ที่มีการขึ้นบรรทัดใหม่ a):

$ find . -type f -print0 | xargs -0 wc
 9  9 38 ./file1
 5  5 20 ./file2
14 14 58 total

ซึ่งมีวิธีการของตัวเองในการทำเช่นนี้โดยไม่จำเป็นต้องใช้xargsเลย:

$ find . -type f -exec wc {} +
 9  9 38 ./file1
 5  5 20 ./file2
14 14 58 total

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

$ for file in *; do wc "$file"; done
 9  9 38 file1
 5  5 20 file2

ด้านปัญหาคือว่าfileไม่จริงอ่าน stdin เว้นแต่ได้รับอย่างชัดเจน-ยึด: เปรียบเทียบfile foo, echo foo | fileและecho foo | file -; ในความเป็นจริงนั่นอาจเป็นเหตุผลที่ข้อความการใช้งานในกรณี OPs (นั่นไม่ใช่เพราะการส่งออกของlsคือ "ข้อความเพียง" แต่เพราะรายการอาร์กิวเมนต์fileเป็นที่ว่างเปล่า)
steeldriver

@steeldriver ใช่ AFAIK เป็นกรณีของโปรแกรมทั้งหมดที่คาดว่าไฟล์และไม่ใช่ข้อความเป็นอินพุต พวกเขาไม่สนใจ stdin โดยค่าเริ่มต้น โปรดทราบecho foo | file -ว่าไม่ได้ทำงานfileกับไฟล์fooแต่อยู่ในสตรีม stdin
terdon

ดีมีเป็ดแปลก ๆ (?!) เช่นcatนั้นยกเว้น stdin โดยไม่ต้อง-ยกเว้นเมื่อได้รับข้อโต้แย้งไฟล์เช่นกันฉันคิดว่า?
ขับเหล็ก

3
คำตอบนี้ล้มเหลวในการอธิบายความแตกต่างระหว่าง stdin และอาร์กิวเมนต์บรรทัดคำสั่งและดังนั้นแม้จะเป็นจุดมากกว่าคำตอบที่ยอมรับก็ยังคงทำให้เข้าใจผิดด้วยเหตุผลเดียวกัน
zwol

5
@terdon ฉันคิดว่านั่นเป็นข้อผิดพลาดร้ายแรงในกรณีนี้ "file (1) รับรายการไฟล์ที่จะทำงานในฐานะอาร์กิวเมนต์บรรทัดคำสั่งไม่ใช่อินพุตมาตรฐาน" เป็นพื้นฐานในการทำความเข้าใจว่าทำไมคำสั่ง OP ไม่ทำงานและความแตกต่างเป็นพื้นฐานของการเขียนสคริปต์เชลล์โดยทั่วไป คุณไม่ได้ทำสิ่งเหล่านั้นโดยการทำให้มันวาว
zwol

6

เรียนรู้ว่า '|' (ไปป์ไลน์) หมายถึงการเปลี่ยนเส้นทางเอาต์พุตจากคำสั่งไปยังอินพุตของอีกอันหนึ่ง

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

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


6

คำตอบที่ได้รับการยอมรับอธิบายว่าทำไมคำสั่งไพพ์ไม่ทำงานในทันทีและด้วยfile *คำสั่งมันจึงเป็นคำตอบที่ง่ายและตรงไปตรงมา

ฉันอยากจะแนะนำทางเลือกอื่นที่อาจมีประโยชน์ในบางครั้ง เคล็ดลับคือการใช้(`)อักขระbacktick backtick จะมีการอธิบายในรายละเอียดมากที่นี่ ในระยะสั้นมันจะเอาท์พุทของคำสั่งล้อมรอบใน backticks และทดแทนมันเป็นสตริงในคำสั่งที่เหลือ

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


ฉันกำลังอ่านหนังสือเกี่ยวกับการใช้บรรทัดคำสั่งบน Linux (ข้อสงสัยมาจากฉันทดลองกับมัน) และบังเอิญฉันเพิ่งอ่านเกี่ยวกับ "การทดแทนคำสั่ง" คุณสามารถใช้ทั้ง$ (คำสั่ง)หรือcommand(ไม่พบรหัสแบ็กสแลชบนโทรศัพท์ของฉัน) เพื่อขยายเอาต์พุตของคำสั่งใน bash และใช้เป็นพารามิเตอร์ในคำสั่งอื่น ๆ มีประโยชน์จริงๆแม้ว่าการใช้งานในกรณีนี้ (ด้วยls ) จะยังคงทำให้เกิดปัญหาบางอย่างเนื่องจากอักขระพิเศษในชื่อไฟล์บางตัว
IanC

@IanC แต่น่าเสียดายที่หนังสือและแบบฝึกหัดส่วนใหญ่เกี่ยวกับ bash นั้นเป็นขยะมีการปนเปื้อนด้วยวิธีปฏิบัติที่ไม่ดีไวยากรณ์ที่เลิกใช้แล้วข้อบกพร่องเล็กน้อย (เพียงอย่างเดียว) การอ้างอิงที่น่าเชื่อถือมีนักพัฒนา bash นั่นคือคู่มือและช่อง#bash IRCบน freenode (ตรวจสอบทรัพยากรที่ลิงก์ในหัวข้อของช่อง)
Ignis

1
การใช้คำสั่งทดแทนอาจมีประโยชน์ในบางครั้ง แต่ในบริบทนี้มันค่อนข้างผิดปกติโดยเฉพาะกับ ls
Joe

ยังเกี่ยวข้องกับ: unix.stackexchange.com/a/5782/107266
2560

5

ผลลัพธ์ของการlsผ่านไปป์เป็นบล็อกข้อมูลที่มี 0x0a แยกแต่ละบรรทัด - เช่นอักขระตัวดึงข้อมูล - และfileรับสิ่งนี้เป็นพารามิเตอร์เดียวซึ่งคาดว่าอักขระหลายตัวทำงานในเวลาเดียวกัน

ตามกฎทั่วไปอย่าใช้lsเพื่อสร้างแหล่งข้อมูลสำหรับคำสั่งอื่น ๆ - วันหนึ่งมันจะ .. ท่อเข้าrmแล้วคุณมีปัญหา!

ดีกว่าที่จะใช้ลูปเช่นfor i in *; do file "$i" ; doneซึ่งจะสร้างผลลัพธ์ที่คุณต้องการคาดการณ์ คำพูดจะมีในกรณีของชื่อไฟล์ที่มีช่องว่าง


8
ง่ายขึ้น: file *;-)
Wayne_Yux

3
@IANC ฉันไม่สามารถเครียดพอที่แยกวิเคราะห์ผลลัพธ์lsเป็นความคิดที่แย่มากๆ ไม่เพียงเพราะคุณอาจส่งต่อสิ่งที่เป็นอันตรายเช่นที่rmสำคัญไปกว่านั้นเพราะมันทำลายชื่อไฟล์ที่ไม่ได้มาตรฐาน
terdon

5
ย่อหน้าแรกอยู่ระหว่างความเข้าใจผิดและเรื่องไร้สาระตรง Linefeeds ไม่มีความเกี่ยวข้อง ย่อหน้าที่สองนั้นถูกต้องด้วยเหตุผลที่ไม่ถูกต้อง มันไม่ดีที่จะแยกคำ ls แต่ไม่ใช่เพราะมันอาจจะ "piped" rm อย่างน่าอัศจรรย์
John Kugelman สนับสนุน Monica

1
ไม่rmใช้ชื่อไฟล์จากอินพุตมาตรฐาน? ผมคิดว่าไม่. นอกจากนี้ตามกฎทั่วไปlsเป็นหนึ่งในตัวอย่างหลักของแหล่งข้อมูลสำหรับการใช้งาน Unix pipelines ตั้งแต่จุดเริ่มต้นของ Unix นั่นเป็นเหตุผลว่าทำไมจึงใช้ค่าเริ่มต้นเป็นชื่อไฟล์ต่อบรรทัดแบบง่าย ๆ โดยไม่มีแอททริบิวหรือการตกแต่งเมื่อเอาท์พุตเป็นไพพ์ซึ่งต่างจากการจัดรูปแบบเริ่มต้นตามปกติเมื่อเอาต์พุตเป็นเทอร์มินัล
davidbak

2
@DewiMorgan เว็บไซต์นี้มีการกำหนดเป้าหมายส่วนใหญ่เป็นผู้ชมที่ไม่ใช่ด้านเทคนิคดังนั้นการแพร่กระจาย / ส่งเสริมนิสัยที่ไม่ดีที่นี่จะเป็นอันตรายและไม่ทำอะไรเลย ใน unix.SE หรือชุมชนเทคโนโลยีอื่น ๆ ที่ผู้ใช้มีความรู้ / วิธีการที่จะตั้งเป้าไว้ใกล้กับเท้าของพวกเขาโดยไม่ต้องยิงเท้าตัวเองจุดของคุณอาจจะถือ (เกี่ยวกับการปฏิบัติอื่น ๆ ) แต่ที่นี่ไม่ทำให้ความคิดเห็นของคุณดูฉลาด
Ignis

4

หากคุณต้องการใช้ไพพ์เพื่อฟีดให้fileใช้ตัวเลือก-fซึ่งตามด้วยชื่อไฟล์ตามปกติ แต่คุณสามารถใช้ยัติภังค์เดียว-เพื่ออ่านจาก stdin ได้เช่นกัน

$ ls
cow.pdf  some.txt
$ ls | file -f -
cow.pdf:       PDF document, version 1.4
some.txt:        ASCII text

เคล็ดลับกับยัติภังค์-ทำงานได้กับ utils บรรทัดคำสั่งมาตรฐาน (แม้ว่า--บางครั้ง) ดังนั้นจึงควรลองดู

เครื่องมือxargมีประสิทธิภาพมากขึ้นและในกรณีส่วนใหญ่จำเป็นเฉพาะในกรณีที่รายการอาร์กิวเมนต์ยาวเกินไป (ดูโพสต์นี้เพื่อดูรายละเอียด)


เมื่อ--ไหร่ ฉันไม่เคยเห็นอย่างนั้น --โดยทั่วไปจะเป็นตัวบ่งชี้ "สิ้นสุดธง"
John Kugelman สนับสนุน Monica

ใช่ แต่ฉันพบมันในสองสามกรณี (ab) ที่ใช้ในวิธีนั้นโดยโปรแกรมเมอร์ ฉันจำไม่ได้ว่าที่ไหน (จะเพิ่มความคิดเห็นถ้าฉัน) แต่ฉันจำคำสาปที่ฉันพูดเมื่อฉันพบมันและคำสาปเหล่านี้แน่นอน NSFW ;-)
deamentiaemundi

2

มันใช้งานคำสั่งเช่นด้านล่าง

ls | xargs file

มันจะทำงานได้ดีขึ้นสำหรับฉัน


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