เมื่อฉันใช้findเพื่อดูไฟล์ทั้งหมดที่ไฟล์ PDF ในไดเรกทอรีที่ผมเห็น/home access deniedเพื่อกำจัดพวกเขาฉันพยายาม:
find /home -iname "*.pdf" | grep -v "access denied"
อย่างไรก็ตามผลลัพธ์เหมือนกัน ฉันจะกำจัดบรรทัดเหล่านี้ได้อย่างไร
เมื่อฉันใช้findเพื่อดูไฟล์ทั้งหมดที่ไฟล์ PDF ในไดเรกทอรีที่ผมเห็น/home access deniedเพื่อกำจัดพวกเขาฉันพยายาม:
find /home -iname "*.pdf" | grep -v "access denied"
อย่างไรก็ตามผลลัพธ์เหมือนกัน ฉันจะกำจัดบรรทัดเหล่านี้ได้อย่างไร
คำตอบ:
สิ่งที่คุณพยายามที่ไม่ได้ทำงานเพราะaccess deniedการส่งออกมีข้อผิดพลาดและส่ง STDERR แทน STDOUT grepซึ่งเป็นประปา
คุณสามารถหลีกเลี่ยงการเห็นข้อผิดพลาดเหล่านั้นด้วยการเปลี่ยนเส้นทางเพียง STDERR
find /home -iname "*.pdf" 2>/dev/null
หรืออย่างที่David Foerster แสดงความคิดเห็นเราสามารถปิด STDERR ได้อย่างกระชับยิ่งขึ้น
find /home -iname "*.pdf" 2>&-
อย่างไรก็ตามฉันสงสัยว่าคุณต้องการค้นหาบ้านของคุณแทนที่จะเป็นผู้ใช้คนอื่นดังนั้นคุณอาจต้องการจริงๆ
find ~ -iname "*.pdf"
หากข้อผิดพลาดอาจแสดงว่ามีเจ้าของที่ไม่ถูกต้องในการกำหนดค่าท้องถิ่นของคุณซึ่งคุณควรตรวจสอบ
sudo chown $USER: ~/.gvfs ~/.dbus
2>&-ใกล้ชิดกับ GNU พบว่าจะไม่ยุติหากพยายามเขียนข้อความแสดงข้อผิดพลาดไปยังตัวบอกความผิดปกติของไฟล์ สำหรับปัญหาที่เป็นเจ้าของจะมีประสิทธิภาพมากขึ้นในกรณีของไฟล์ภายในที่ไม่ได้เป็นเจ้าของsudo chown -R $USER: ... $USER
การเข้าถึงปฏิเสธอาจจะถูกพิมพ์ลงบนมากกว่าstderrstdout
ลองสิ่งนี้:
find /home -iname "*.pdf" 2>&1 | grep -v "access denied"
การ2>&1เปลี่ยนเส้นทางผลลัพธ์จากstderrไปยังstdoutเพื่อให้grep -vสามารถทำงานได้ (โดยค่าเริ่มต้น|จะมีเพียงท่อstdoutและไม่stderrเท่านั้น)
2>&1... ฉันไม่ใช่ผู้เชี่ยวชาญทุบตีดังนั้นถ้าไม่ถูกต้องแล้วโปรดพูดอย่างนั้น :)
bashในอูบุนตูมียกเว้นในโหมด POSIX ฉันคิดว่านี่เป็นทางออกที่ดีที่สุด - ไฟล์ที่มีชื่อประสงค์ร้ายaccess deniedจะยังคงปรากฏ
คุณอาจหมายถึง "การอนุญาตที่ถูกปฏิเสธ" ซึ่งเป็นสิ่งที่findในอูบุนตูแสดงให้คุณเห็นเมื่อคุณไม่สามารถเข้าถึงบางสิ่งได้เนื่องจากสิทธิ์ของไฟล์ - แทนที่จะเป็น "การเข้าถึงถูกปฏิเสธ"
คำสั่งทั่วไปอย่างครบถ้วนหนึ่งคำสั่งที่ทำอย่างถูกต้อง (และเป็นโบนัสสามารถพกพาไปยัง*อื่น ๆ ได้ตราบใดที่ข้อความแสดงข้อผิดพลาดเหมือนกัน) คือ:
(find 3>&1 1>&2 2>&3 3>&- | grep -Fv 'Permission denied') 3>&1 1>&2 2>&3 3>&-
(โดยปกติคุณต้องการส่งอาร์กิวเมนต์บางอย่างไปที่findสิ่งเหล่านั้นไปก่อนการเปลี่ยนเส้นทางครั้งแรก3>&1)
อย่างไรก็ตามบ่อยครั้งที่คุณจะสามารถใช้สิ่งที่ง่ายกว่า ตัวอย่างเช่นคุณอาจจะสามารถใช้ทดแทนกระบวนการ รายละเอียดดังนี้
วิธีทั่วไปสองวิธีคือการทิ้งstderr (ตามคำตอบของ Zanna ) หรือเปลี่ยนเส้นทาง stderr ไปยังstdoutและกรอง stdout (เช่นเดียวกับในคำตอบของ Android Dev ) แม้ว่าพวกเขาจะมีข้อได้เปรียบในการเขียนง่าย ๆ และมักเป็นทางเลือกที่สมเหตุสมผล แต่วิธีการเหล่านี้ไม่เหมาะ
การละทิ้งทุกสิ่งที่ส่งไปยัง stderrเช่นโดยการเปลี่ยนเส้นทางไปยังอุปกรณ์ nullด้วย2>/dev/nullหรือโดยปิดด้วย2>&-- ทำให้มีความเสี่ยงที่จะเกิดข้อผิดพลาดที่ขาดหายไปนอกจาก "ปฏิเสธสิทธิ์"
"การอนุญาตที่ถูกปฏิเสธ" อาจเป็นข้อผิดพลาดที่พบบ่อยที่สุดเมื่อเรียกใช้findแต่มันอยู่ไกลจากข้อผิดพลาดที่เป็นไปได้เท่านั้นและหากมีข้อผิดพลาดเกิดขึ้นอีกคุณอาจต้องการทราบเกี่ยวกับมัน โดยเฉพาะอย่างยิ่งfindรายงาน "ไม่มีไฟล์หรือไดเรกทอรีดังกล่าว" หากไม่มีจุดเริ่มต้น ด้วยจุดเริ่มต้นหลายจุดfindอาจยังคงให้ผลลัพธ์ที่มีประโยชน์และปรากฏว่าใช้งานได้ ตัวอย่างเช่นถ้าaและcอยู่ แต่bไม่ได้find a b c -name xพิมพ์ผลในaแล้ว "ไม่มีไฟล์หรือไดเรกทอรีดังกล่าว" สำหรับแล้วผลในการbc
การรวม stdout และ stderr เข้าด้วยกันเป็น stdout และไพพ์ไปยังgrepหรือคำสั่งอื่น ๆ เพื่อกรอง - เช่นเดียวกับ2>&1 | grep ...หรือ|& grep ...- เสี่ยงต่อการกรองไฟล์โดยไม่ได้ตั้งใจซึ่งชื่อนั้นมีข้อความที่ถูกกรอง
ตัวอย่างเช่นหากคุณกรองบรรทัดที่มี "สิทธิ์ที่ถูกปฏิเสธ" จากนั้นคุณจะวางผลการค้นหาที่แสดงชื่อไฟล์เช่น "สิทธิ์ที่ถูกปฏิเสธข้อความ. txt" อาจเกิดขึ้นโดยไม่ได้ตั้งใจแม้ว่าจะเป็นไปได้ที่ไฟล์จะได้รับชื่อที่ประดิษฐ์ขึ้นเป็นพิเศษเพื่อป้องกันการค้นหาของคุณ
การกรองสตรีมแบบรวมมีปัญหาอื่นซึ่งไม่สามารถบรรเทาได้ด้วยการกรองแบบเลือกใช้มากขึ้น (เช่นด้วยgrep -vx 'find: .*: Permission denied'ที่ด้านขวาของไพพ์) findการดำเนินการบางอย่างรวมถึงการ-printกระทำที่เป็นนัยเมื่อคุณระบุไม่มีการดำเนินการกำหนดวิธีการส่งออกชื่อไฟล์โดยพิจารณาว่าstdoutเป็นเทอร์มินัลหรือไม่
?ถูกพิมพ์แทนgrep) ทำให้findไม่เห็นเทอร์มินัลอีกต่อไป (แม่นยำยิ่งขึ้นทำให้ stdout ไม่ใช่เทอร์มินัล) จากนั้นอักขระแปลก ๆ จะถูกส่งออกอย่างแท้จริง แต่ถ้าคำสั่งทั้งหมดทางด้านขวาของไปป์นั้นคือ(a)ลบบรรทัดที่ดูเหมือนข้อความ "สิทธิ์ที่ถูกปฏิเสธ" และ(b)พิมพ์สิ่งที่เหลืออยู่คุณจะยังคงอยู่ภายใต้ประเภทของ shenanigans ที่findเทอร์มินัล การตรวจจับมีวัตถุประสงค์เพื่อป้องกันman findสำหรับข้อมูลเพิ่มเติมรวมถึงพฤติกรรมของแต่ละการกระทำที่พิมพ์ชื่อไฟล์ ( "หลายการกระทำของผลการค้นหาในการพิมพ์ของข้อมูลซึ่งอยู่ภายใต้การควบคุมของผู้ใช้อื่น ๆ ..." ) ดูเพิ่มเติมส่วน3.3.2.1 , 3.3.2.2และ3.3.2.3ของคู่มืออ้างอิง GNU Findutilsการสนทนาข้างต้นของชื่อไฟล์ผิดปกติที่เกี่ยวข้องกับGNU findซึ่งเป็นการfindใช้งานในระบบ GNU / Linux รวมถึง Ubuntu
สิ่งที่คุณจริงๆต้องการที่นี่คือการปล่อยstdoutเหมือนเดิมในขณะที่ท่อstderrgrepไป น่าเสียดายที่ไม่มีเรื่องง่ายสำหรับเรื่องนี้ |pipes stdout และเชลล์บางตัว (รวมถึงbash) รองรับ|&ไพพ์ทั้งสองสตรีมหรือคุณสามารถเปลี่ยนเส้นทาง stderr ไปยัง stdout ก่อนด้วย2>&1 |ซึ่งมีผลเหมือนกัน แต่เชลล์ที่ใช้กันทั่วไปไม่ได้มีไวยากรณ์ให้กับไปป์ stderr เท่านั้น
คุณยังสามารถทำสิ่งนี้ได้ มันน่าอึดอัดใจ วิธีหนึ่งคือการสลับ stdout กับ stderrเพื่อให้ผลการค้นหาอยู่ใน stderr และข้อผิดพลาดอยู่ใน stdout จากนั้นไปที่ stdout ไปที่grepเพื่อกรอง:
find args 3>&1 1>&2 2>&3 3>&- | grep -Fv 'Permission denied'
โดยปกติคุณจะส่งผ่านข้อโต้แย้งไปยังfindเช่นจุดเริ่มต้น (สถานที่ที่จะค้นหาซึ่งมักจะเป็นไดเรกทอรี) และภาคแสดง (การทดสอบและการกระทำ) การเดินทางเหล่านี้ในสถานที่ของargsดังกล่าวข้างต้น
สิ่งนี้ทำงานได้โดยการแนะนำfile descriptorใหม่เพื่อเก็บหนึ่งในสองสตรีมมาตรฐานที่คุณต้องการสลับ, ทำการเปลี่ยนเส้นทางเพื่อสลับพวกเขาและปิดตัวบ่งชี้ไฟล์ใหม่
3>&1 เปลี่ยนเส้นทาง file descriptor 3 ไปที่ stdout ดังนั้นเมื่อ stdout (file descriptor 1) ถูกเปลี่ยนเส้นทางในภายหลัง stdout ดั้งเดิมยังคงสามารถเขียนได้อย่างง่ายดาย1>&2เปลี่ยนเส้นทาง stdout ไปยัง stderr เนื่องจาก file descriptor 3 ยังคงเป็น stdout ดั้งเดิมจึงยังสามารถเข้าถึงได้2>&3 เปลี่ยนเส้นทาง stderr ไปยัง file descriptor 3 ซึ่งเป็น stdout ดั้งเดิม3>&- ปิดไฟล์ descriptor 3 ซึ่งไม่จำเป็นอีกต่อไปแต่วิธีนี้มีข้อเสียที่ผลการค้นหาจะถูกส่งไป stderr และข้อผิดพลาดจะถูกส่งไป stdout หากคุณกำลังรันคำสั่งนี้โดยตรงในเชลล์แบบโต้ตอบและไม่ส่งต่อหรือเปลี่ยนเส้นทางเอาต์พุตอีกต่อไปนั่นจะไม่สำคัญ ไม่เช่นนั้นอาจเป็นปัญหา หากคุณวางคำสั่งนั้นไว้ในสคริปต์จากนั้นบางคน (อาจเป็นคุณในภายหลัง) เปลี่ยนเส้นทางหรือไพพ์เอาต์พุตมันจะไม่ทำงานตามที่คาดไว้
วิธีแก้ปัญหาคือการสลับสตรีมกลับหลังจากกรองเอาท์พุทเสร็จแล้ว การใช้การเปลี่ยนเส้นทางแบบเดียวกันที่แสดงด้านบนทางด้านขวาของไปป์ไลน์จะไม่บรรลุเป้าหมายนี้เพราะ|ไปป์ stdout เท่านั้นดังนั้นด้านข้างของไปป์ไลน์จะรับเอาท์พุทที่ส่งไปยัง stderr เท่านั้น (เพราะกระแสถูกเปลี่ยน) และไม่ใช่ต้นฉบับ เอาต์พุต stdout แต่คุณสามารถใช้( )เพื่อเรียกใช้คำสั่งข้างต้นใน subshell ( ที่เกี่ยวข้อง ) จากนั้นใช้การเปลี่ยนเส้นทางการแลกเปลี่ยนกับที่:
(find args 3>&1 1>&2 2>&3 3>&- | grep -Fv 'Permission denied') 3>&1 1>&2 2>&3 3>&-
มันคือการจัดกลุ่มไม่ใช่เฉพาะ subshell ซึ่งทำให้งานนี้ หากคุณต้องการคุณสามารถใช้{ ;}:
{ find args 3>&1 1>&2 2>&3 3>&- | grep -Fv 'Permission denied'; } 3>&1 1>&2 2>&3 3>&-
เชลล์บางตัวรวมถึง Bash ในระบบที่สามารถรองรับได้ (รวมถึงระบบ GNU / Linux เช่น Ubuntu) ให้คุณทำการทดแทนกระบวนการซึ่งช่วยให้คุณสามารถรันคำสั่งและเปลี่ยนเส้นทางไปยัง / จากสตรีมของหนึ่งในนั้น คุณสามารถเปลี่ยนเส้นทางfindคำสั่ง stderr ไปยังgrepคำสั่งที่กรองและเปลี่ยนเส้นทางgrepstdout ของคำสั่งนั้นไปยัง stderr
find args 2> >(grep -Fv 'Permission denied' >&2)
เครดิตไปที่Android Devสำหรับแนวคิดนี้
1>&2ทั้งสองตัวอย่างผู้ใช้ ฉันได้ใช้>&2, ซึ่งเทียบเท่า ( ที่เกี่ยวข้อง ) ใช้ตามที่คุณต้องการแม้ว่าจะbash รองรับการทดแทนกระบวนการ แต่shใน Ubuntuก็dashไม่เป็นเช่นนั้น มันจะให้ "ข้อผิดพลาดทางไวยากรณ์: การเปลี่ยนเส้นทางที่ไม่คาดคิด" หากคุณพยายามใช้วิธีนี้ในขณะที่วิธีการเปลี่ยน stdout และ stderr จะยังคงใช้งานได้ นอกจากนี้เมื่อbashทำงานในโหมด POSIXการสนับสนุนการทดแทนกระบวนการจะถูกปิด
หนึ่งในสถานการณ์ที่bashวิ่งในโหมด POSIX คือเมื่อมันถูกเรียกว่าเป็น1sh ดังนั้นบนระบบปฏิบัติการเช่น Fedora ที่bashให้บริการ/bin/shหรือถ้าคุณสร้าง/bin/shจุดเชื่อมโยงให้กับbashตัวเองบน Ubuntu การทดแทนกระบวนการยังไม่สามารถใช้งานได้ในshสคริปต์โดยไม่ต้องใช้คำสั่งก่อนหน้าเพื่อปิดโหมด POSIX ทางออกที่ดีที่สุดของคุณหากคุณต้องการใช้วิธีนี้ในสคริปต์คือการใส่#!/bin/bash ที่ด้านบนแทน#!/bin/shถ้าคุณยังไม่ได้
1 : ในสถานการณ์นี้bashเปิดโหมด POSIX โดยอัตโนมัติหลังจากที่รันคำสั่งในสคริปต์เริ่มต้น
การทดสอบคำสั่งเหล่านี้มีประโยชน์ การทำเช่นนี้ฉันจะสร้างtmpไดเรกทอรีย่อยของไดเรกทอรีปัจจุบันและเติมมันมีบางไฟล์และไดเรกทอรีสละสิทธิ์ห่างจากหนึ่งของพวกเขาที่จะเรียก "ไม่อนุญาต" findข้อผิดพลาดใน
mkdir tmp; cd tmp; mkdir a b c; touch w a/x 'a/Permission denied messages.txt' b/y c/z; chmod 0 b
หนึ่งในไดเรกทอรีที่เป็นเข้าถึงรวมถึงไฟล์ที่มี "ไม่อนุญาต" อยู่ในชื่อ การรันfindโดยไม่มีการเปลี่ยนเส้นทางหรือไพพ์แสดงไฟล์นี้ แต่ยังแสดงข้อผิดพลาด "สิทธิ์ที่ถูกปฏิเสธ" จริงสำหรับไดเรกทอรีอื่นที่ไม่สามารถเข้าถึงได้:
ek@Io:~/tmp$ find
.
./a
./a/Permission denied messages.txt
./a/x
./c
./c/z
./w
./b
find: ‘./b’: Permission denied
การไปป์grepไลน์ทั้ง stdout และ stderr ไปยังและการกรองบรรทัดที่มี "สิทธิ์ที่ถูกปฏิเสธ" ทำให้ข้อความแสดงข้อผิดพลาดหายไป แต่ยังซ่อนผลลัพธ์การค้นหาไฟล์ที่มีวลีนั้นในชื่อ:
ek@Io:~/tmp$ find |& grep -Fv 'Permission denied'
.
./a
./a/x
./c
./c/z
./w
./b
find 2>&1 | grep -Fv 'Permission denied' เทียบเท่าและสร้างผลลัพธ์เดียวกัน
วิธีการที่แสดงด้านบนสำหรับการกรอง "ปฏิเสธสิทธิ์" จากข้อความแสดงข้อผิดพลาดเท่านั้นและไม่ได้มาจากผลการค้นหา - จะทำได้สำเร็จ ตัวอย่างเช่นต่อไปนี้เป็นวิธีการสลับ stdout และ stderr:
ek@Io:~/tmp$ (find 3>&1 1>&2 2>&3 3>&- | grep -Fv 'Permission denied') 3>&1 1>&2 2>&3 3>&-
.
./a
./a/Permission denied messages.txt
./a/x
./c
./c/z
./w
./b
find args 2> >(grep -Fv 'Permission denied' >&2) สร้างเอาต์พุตเดียวกัน
คุณสามารถทริกเกอร์ข้อความแสดงข้อผิดพลาดที่แตกต่างกันเพื่อให้แน่ใจว่าบรรทัดที่ส่งไปยัง stderr ที่ไม่มีข้อความ "ปฏิเสธการอนุญาต" ยังคงได้รับอนุญาตผ่าน ตัวอย่างเช่นที่นี่ฉันได้ทำงานfindกับไดเรกทอรีปัจจุบัน ( .) เป็นจุดเริ่มต้นหนึ่ง แต่ไดเรกทอรีไม่มีอยู่fooอีก:
ek@Io:~/tmp$ (find . foo 3>&1 1>&2 2>&3 3>&- | grep -Fv 'Permission denied') 3>&1 1>&2 2>&3 3>&-
.
./a
./a/Permission denied messages.txt
./a/x
./c
./c/z
./w
./b
find: ‘foo’: No such file or directory
findเอาต์พุตมาตรฐานนั้นยังคงเป็นเทอร์มินัลนอกจากนี้เรายังสามารถดูว่าคำสั่งใดทำให้อักขระพิเศษเช่นขึ้นบรรทัดใหม่ปรากฏขึ้นอย่างแท้จริง (สามารถทำได้แยกต่างหากจากการสาธิตด้านบนและไม่จำเป็นต้องอยู่ในtmpไดเรกทอรี)
สร้างไฟล์ด้วยบรรทัดใหม่ในชื่อ:
touch $'abc\ndef'
โดยปกติเราจะใช้ไดเรกทอรีเป็นจุดเริ่มต้นfindแต่ไฟล์ทำงานด้วย:
$ find abc*
abc?def
ท่อ stdout คำสั่งอื่นทำให้เกิดการขึ้นบรรทัดใหม่ที่จะออกมาอย่างแท้จริงในการสร้างความประทับใจที่ผิดพลาดของผลการค้นหาสองแยกและabc defเราสามารถทดสอบด้วยcat:
$ find abc* | cat
abc
def
การเปลี่ยนเส้นทาง just stderr ไม่ได้ทำให้เกิดปัญหานี้:
$ find abc* 2>/dev/null
abc?def
และไม่ปิดมัน:
$ find abc* 2>&-
abc?def
การไปป์ที่grep จะทำให้เกิดปัญหา:
$ find abc* |& grep -Fv 'Permission denied'
abc
def
(การแทนที่|&ด้วย2>&1 |ค่าเทียบเท่าและสร้างเอาต์พุตเดียวกัน)
การสลับ stdout และ stderr และ piping stdout ไม่ทำให้เกิดปัญหาfindstdout ของกลายเป็น stderr ซึ่งไม่ได้ถูกpiped:
$ find abc* 3>&1 1>&2 2>&3 3>&- | grep -Fv 'Permission denied'
abc?def
การจัดกลุ่มคำสั่งนั้นและการสลับกระแสกลับไม่ทำให้เกิดปัญหา:
$ (find abc* 3>&1 1>&2 2>&3 3>&- | grep -Fv 'Permission denied') 3>&1 1>&2 2>&3 3>&-
abc?def
( { ;}เวอร์ชันสร้างเอาต์พุตเดียวกัน)
การใช้การทดแทนกระบวนการเพื่อกรอง stderr ไม่ทำให้เกิดปัญหาเช่นกัน:
$ find abc* 2> >(grep -Fv 'Permission denied' >&2)
abc?def