เมื่อฉันใช้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
การเข้าถึงปฏิเสธอาจจะถูกพิมพ์ลงบนมากกว่าstderr
stdout
ลองสิ่งนี้:
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
แล้ว "ไม่มีไฟล์หรือไดเรกทอรีดังกล่าว" สำหรับแล้วผลในการb
c
การรวม 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
คำสั่งที่กรองและเปลี่ยนเส้นทางgrep
stdout ของคำสั่งนั้นไปยัง 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 ไม่ทำให้เกิดปัญหาfind
stdout ของกลายเป็น 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