เป็นไปได้หรือไม่ที่จะใช้ `find -exec sh -c` อย่างปลอดภัย?


29

ฉันพยายามที่จะใช้findเพื่อecho 0เป็นไฟล์บาง แต่เห็นได้ชัดว่านี้จะทำงานเฉพาะกับsh -c:

find /proc/sys/net/ipv6 -name accept_ra -exec sh -c 'echo 0 > {}' \;

แต่การใช้งานsh -cด้วยfind -execทำให้ฉันรู้สึกไม่สบายใจมากเพราะฉันสงสัยว่าจะมีปัญหาในการอ้างอิง ฉันเล่นซอกับมันและเห็นได้ชัดว่าความสงสัยของฉันถูกต้อง:

  • การตั้งค่าการทดสอบของฉัน:

    martin@dogmeat ~ % cd findtest 
    martin@dogmeat ~/findtest % echo one > file\ with\ spaces
    martin@dogmeat ~/findtest % echo two > file\ with\ \'single\ quotes\'
    martin@dogmeat ~/findtest % echo three > file\ with\ \"double\ quotes\"
    martin@dogmeat ~/findtest % ll
    insgesamt 12K
    -rw-rw-r-- 1 martin martin 6 Sep 17 12:01 file with "double quotes"
    -rw-rw-r-- 1 martin martin 4 Sep 17 12:01 file with 'single quotes'
    -rw-rw-r-- 1 martin martin 4 Sep 17 12:01 file with spaces
    
  • ดูเหมือนว่าการใช้find -execโดยไม่sh -cทำงานจะไม่มีปัญหา - ไม่จำเป็นต้องมีการอ้างถึงที่นี่:

    martin@dogmeat ~ % find findtest -type f -exec cat {} \;
    one
    two
    three
    
  • แต่เมื่อฉันใช้sh -c {}ดูเหมือนจะต้องมีข้อความบางอย่าง:

    martin@dogmeat ~ % LANG=C find findtest -type f -exec sh -c 'cat {}' \;
    cat: findtest/file: No such file or directory
    cat: with: No such file or directory
    cat: spaces: No such file or directory
    cat: findtest/file: No such file or directory
    cat: with: No such file or directory
    cat: single quotes: No such file or directory
    cat: findtest/file: No such file or directory
    cat: with: No such file or directory
    cat: double quotes: No such file or directory
    
  • เครื่องหมายคำพูดคู่ทำงานตราบใดที่ไม่มีชื่อไฟล์มีเครื่องหมายคำพูดคู่:

    martin@dogmeat ~ % LANG=C find findtest -type f -exec sh -c 'cat "{}"' \;
    one
    two
    cat: findtest/file with double: No such file or directory
    cat: quotes: No such file or directory
    
  • อัญประกาศเดี่ยวทำงานตราบใดที่ไม่มีชื่อไฟล์มีเครื่องหมายคำพูดเดี่ยว:

    martin@dogmeat ~ % LANG=C find findtest -type f -exec sh -c "cat '{}'" \;
    one
    cat: findtest/file with single: No such file or directory
    cat: quotes: No such file or directory
    three

ฉันไม่พบวิธีแก้ไขปัญหาที่ใช้งานได้ในทุกกรณี มีสิ่งที่ฉันมองเห็นหรือใช้sh -cในที่find -execไม่ปลอดภัยโดยเนื้อแท้?

คำตอบ:


40

ไม่เคยฝัง{}ในรหัสเปลือก! ที่สร้างช่องโหว่การฉีดคำสั่ง ทราบว่าcat "{}"มันไม่เพียง แต่เกี่ยวกับ"ตัวอักษร\, `, $นอกจากนี้ยังมีปัญหา (พิจารณาเช่นไฟล์ที่เรียกว่า./$(reboot)/accept_ra)

(โดยวิธีการใช้งานบางอย่างfindจะไม่ยอมให้คุณทำเช่นนั้นและPOSIX ออกจากพฤติกรรมที่ไม่ได้ระบุเมื่อ{}ไม่ได้อยู่ในการโต้แย้งfind)

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

find . -name accept_ra -exec sh -c 'echo 0 > "$1"' sh {} \;

หรือเพื่อหลีกเลี่ยงการรันหนึ่งshไฟล์ต่อหนึ่งไฟล์:

find . -name accept_ra -exec sh -c 'for file do
  echo 0 > "$file"; done' sh {} +

เช่นเดียวกับxargs -I{}หรือ'szsh zargs -I{}อย่าเขียน:

<list.txt xargs -I {} sh -c 'cmd> {}'

ซึ่งจะเป็นช่องโหว่การฉีดคำสั่งเช่นเดียวกับfindข้างต้น แต่:

<list.txt xargs sh -c 'for file do cmd > "$file"; done' sh

ซึ่งยังมีประโยชน์ในการหลีกเลี่ยงการเรียกใช้หนึ่งshไฟล์ต่อหนึ่งไฟล์และข้อผิดพลาดเมื่อlist.txtไม่มีไฟล์ใด ๆ

กับzshของzargsคุณอาจต้องการที่จะใช้ฟังก์ชั่นมากกว่าการเรียกใช้sh -c:

do-it() cmd > $1
zargs ./*.txt -- do-it

หมายเหตุว่าในทุกตัวอย่างข้างต้นที่สองดังกล่าวข้างต้นจะเข้าสู่การของสคริปต์แบบอินไลน์sh $0คุณควรใช้สิ่งที่เกี่ยวข้องมี (ชอบshหรือfind-sh) ไม่ใช่สิ่งที่ชอบ_, -, --หรือสตริงที่ว่างเปล่าเป็นค่าในการ$0ใช้สำหรับข้อความผิดพลาดของเปลือก:

$ find . -name accept_ra -exec sh -c 'echo 0 > "$1"' inline-sh {} \;
inline-sh: ./accept_ra: Permission denied

GNU parallelทำงานแตกต่างกัน ด้วยคุณไม่ได้ต้องการที่จะใช้sh -cเป็นparallelไม่ใช้เปลือกแล้วและพยายามที่จะเข้ามาแทนที่{}ด้วยการโต้แย้งอ้างในไวยากรณ์ที่เหมาะสมสำหรับเปลือก

<list.txt PARALLEL_SHELL=sh parallel 'cmd > {}'

thats ที่สองshน่าจะเป็นชนิดของตัวยึดบางส่วนก็ทำงานเกินไปถ้าแทนที่ด้วย_ตัวอย่างเช่น - มีประโยชน์มากถ้าคุณต้องการที่จะเรียก internals find /tmp -name 'fil*' -exec bash -c 'printf "%q\n" "$1"' _ {} \;ทุบตี: แต่ไม่มีใครรู้ว่าเอกสารนี้อยู่ที่ไหน?
Florian Fida

1
@FlorianFida อาร์กิวเมนต์แรกของเชลล์จะกลายเป็น$0(โดยปกติจะเป็นชื่อของเชลล์คุณต้องข้ามไปในสถานการณ์นี้ดังนั้นจึงไม่กินอาร์กิวเมนต์ตำแหน่งใดตำแหน่งหนึ่งตามปกติของคุณเอกสารสำหรับการ-cกล่าวถึงนี้
Etan Reisner


1
@phk นั่นไม่ใช่argv[0]ที่นี่นั่นเป็นเพียง$0สคริปต์ หน้าของ Sven ไม่ถูกต้องที่นี่rจะไม่ทำให้เปลือกใส่โหมดที่ จำกัด เท่าที่สามารถบอกได้และจะไม่เปลี่ยนโหมดการอยู่บนพื้นฐานของzsh จะเรียกใช้ข้อ จำกัดแต่ไม่) $0(exec -a rksh ksh -c 'cd /')kshksh -c 'cd /' rksh
Stéphane Chazelas

1
สเตฟานถ้าคุณไม่รังเกียจมีคำตอบของคุณที่อธิบายว่า "ทำไมไม่ฝัง {} ในรหัสเชลล์"? ฉันไปที่คำตอบทั้งหมดของคุณภายใต้ค้นหาแต่ไม่สามารถหาได้แม้ว่าฉันจะสาบานได้ฉันเห็นบางสิ่งที่คุณเขียนเกี่ยวกับเรื่องนี้ แต่จำไม่ได้ว่ามันเป็นคำตอบความคิดเห็นในการแชทหรือในเว็บไซต์อื่นเช่น compunix ... หาก / เมื่อคุณมีเวลาคุณจะขยายความในส่วน "ไม่เคยฝัง {}" หรือคุณคิดว่าเราควรมี Q เฉพาะเช่น"ผลกระทบด้านความปลอดภัยของการใช้findกับ-exec sh -cและฝัง{}ในรหัสเชลล์"หรือไม่?
don_crissti
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.