คำสั่ง Linux: จะ 'ค้นหา' เฉพาะไฟล์ข้อความได้อย่างไร?


100

หลังจากค้นหาจาก Google ไม่กี่ครั้งสิ่งที่ฉันได้รับคือ:

find my_folder -type f -exec grep -l "needle text" {} \; -exec file {} \; | grep text

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

คำตอบ:


184

ฉันรู้ว่านี่เป็นเธรดเก่า แต่ฉันเจอมันและคิดว่าฉันจะแชร์วิธีการของฉันซึ่งฉันพบว่าเป็นวิธีที่รวดเร็วมากในการใช้findค้นหาเฉพาะไฟล์ที่ไม่ใช่ไบนารี:

find . -type f -exec grep -Iq . {} \; -print

-Iตัวเลือกในการ grep บอกว่ามันจะไม่สนใจไฟล์ไบนารีและทันที.ตัวเลือกพร้อมกับ-qจะทำให้มันทันทีตรงกับไฟล์ข้อความจึงไปอย่างรวดเร็วมาก คุณสามารถเปลี่ยน-printเป็น a -print0สำหรับท่อเป็นไฟล์xargs -0หรือบางอย่างได้หากคุณกังวลเกี่ยวกับช่องว่าง (ขอบคุณสำหรับเคล็ดลับ @ lucas.werkmeister!)

นอกจากนี้จุดแรกยังจำเป็นสำหรับ BSD บางเวอร์ชันfindเช่นบน OS X แต่ก็ไม่ได้เสียหายอะไรเพียงแค่มีมันอยู่ตลอดเวลาหากคุณต้องการใส่สิ่งนี้ในนามแฝงหรืออะไรบางอย่าง

แก้ไข : ตามที่ @ruslan ระบุไว้อย่างถูกต้อง-andสามารถละเว้นได้เนื่องจากมีการกล่าวโดยนัย


16
ใน Mac OS X ฉันต้องเปลี่ยนเป็นfind . -type f -exec grep -Il "" {} \;ไฟล์.
Alec Jacobson

3
ดีกว่าคำตอบของ peoro เพราะ 1. ตอบคำถามได้จริง 2. ไม่ให้ผลบวกปลอม 3. มีประสิทธิภาพมากกว่า
user123444555621

3
นอกจากนี้คุณยังสามารถใช้find -type f -exec grep -Iq . {} \; -and -printที่มีประโยชน์ที่จะช่วยให้ไฟล์ในfind; คุณสามารถแทนที่-printด้วยไฟล์อื่น-execที่ใช้สำหรับไฟล์ข้อความเท่านั้น (หากคุณปล่อยให้grepพิมพ์ชื่อไฟล์คุณจะไม่สามารถแยกแยะชื่อไฟล์ที่มีขึ้นบรรทัดใหม่ได้)
Lucas Werkmeister

1
@ NathanS.Watson-Haigh ไม่ควรเพราะควรจับคู่ไฟล์ข้อความทันที คุณมีกรณีการใช้งานเฉพาะที่คุณสามารถแบ่งปันได้หรือไม่?
crudcore

2
find . -type f -exec grep -Il . {} +เร็วกว่ามาก ข้อเสียเปรียบคือไม่สามารถขยายได้อีก-execตามที่ @ lucas.werkmeister แนะนำ
Henning


10

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

function findTextInAsciiFiles {
    # usage: findTextInAsciiFiles DIRECTORY NEEDLE_TEXT
    find "$1" -type f -exec grep -l "$2" {} \; -exec file {} \; | grep text
}

ใส่ไว้ในของคุณ.bashrcแล้วเรียกใช้:

findTextInAsciiFiles your_folder "needle text"

เมื่อไหร่ก็ได้ที่คุณต้องการ.


แก้ไขเพื่อสะท้อนการแก้ไขของ OP:

หากคุณต้องการตัดข้อมูลละครใบ้คุณสามารถเพิ่มขั้นตอนเพิ่มเติมให้กับไปป์ไลน์ที่กรองข้อมูลละครใบ้ออกไป นี้ควรทำเคล็ดลับโดยใช้เวลาเพียงแค่สิ่งที่มาก่อน:: cut -d':' -f1:

function findTextInAsciiFiles {
    # usage: findTextInAsciiFiles DIRECTORY NEEDLE_TEXT
    find "$1" -type f -exec grep -l "$2" {} \; -exec file {} \; | grep text | cut -d ':' -f1
}

ฉันไม่แน่ใจว่า "ข้อความ grep" ถูกต้องเพียงพอที่จะรับไฟล์ข้อความทั้งหมดหรือไม่ฉันหมายถึงมีไฟล์ข้อความประเภทใดบ้างที่ไม่มี "ข้อความ" ในสตริงของคำอธิบายประเภทละครใบ้
datasn.io

@ kavoir.com: ใช่ จากfileคู่มือ: "ผู้ใช้ขึ้นอยู่กับการทราบว่าไฟล์ที่อ่านได้ทั้งหมดในไดเร็กทอรีมีคำว่า" text "พิมพ์อยู่"
peoro

2
จะไม่ฉลาดกว่านี้อีกหน่อยที่จะค้นหาไฟล์ข้อความก่อนทำการ grepping แทนที่จะใช้ grepping แล้วกรองไฟล์ข้อความออกไป
ไม่ทราบผู้ใช้

/proc/meminfo, /proc/cpuinfoฯลฯ เป็นไฟล์ข้อความ แต่พูดว่าfile /proc/meminfo /proc/meminfo: emptyฉันสงสัยว่าควรทดสอบ "ว่าง" นอกเหนือจาก "ข้อความ" แต่ไม่แน่ใจว่าประเภทอื่น ๆ สามารถรายงานว่า "ว่างเปล่า" ได้หรือไม่
Timo Kähkönen

“ ทำไมมันไม่สะดวก” - "แสดงข้อความที่ไม่จำเป็น" คำตอบนี้ไม่ได้หมายความว่า
user123444555621

4
find . -type f -print0 | xargs -0 file | grep -P text | cut -d: -f1 | xargs grep -Pil "search"

น่าเสียดายที่ไม่ประหยัดพื้นที่ การใส่สิ่งนี้ลงใน bash script ทำให้ง่ายขึ้นเล็กน้อย

นี่คือพื้นที่ปลอดภัย:

#!/bin/bash
#if [ ! "$1" ] ; then
    echo "Usage: $0 <search>";
    exit
fi

find . -type f -print0 \
  | xargs -0 file \
  | grep -P text \
  | cut -d: -f1 \
  | xargs -i% grep -Pil "$1" "%"

2
มีปัญหาสองสามอย่างในสคริปต์ของคุณ: 1. ถ้าไฟล์ไบนารีถูกตั้งชื่อtext.binล่ะ? 2. ถ้าชื่อไฟล์มี a :?
thkala

3

อีกวิธีในการทำสิ่งนี้:

# find . |xargs file {} \; |grep "ASCII text"

หากคุณต้องการไฟล์เปล่าด้วย:

#  find . |xargs file {} \; |egrep "ASCII text|empty"

2

แล้วสิ่งนี้ล่ะ:

$ grep -rl "needle text" my_folder | tr '\n' '\0' | xargs -r -0 file | grep -e ':[^:]*text[^:]*$' | grep -v -e 'executable'

หากคุณต้องการชื่อไฟล์ที่ไม่มีประเภทไฟล์ให้เพิ่มsedตัวกรองขั้นสุดท้าย

$ grep -rl "needle text" my_folder | tr '\n' '\0' | xargs -r -0 file | grep -e ':[^:]*text[^:]*$' | grep -v -e 'executable' | sed 's|:[^:]*$||'

คุณสามารถกรองประเภทไฟล์ที่ไม่จำเป็นออกได้โดยเพิ่ม-e 'type'ตัวเลือกเพิ่มเติมให้กับgrepคำสั่งสุดท้าย

แก้ไข:

หากxargsเวอร์ชันของคุณรองรับ-dตัวเลือกคำสั่งข้างต้นจะง่ายขึ้น:

$ grep -rl "needle text" my_folder | xargs -d '\n' -r file | grep -e ':[^:]*text[^:]*$' | grep -v -e 'executable' | sed 's|:[^:]*$||'

ฉันโง่ ไม่สังเกตเห็น grep แบบเรียกซ้ำ อย่างที่ฉันเข้าใจว่ามันค่อนข้างเร็วจริง ๆ แม้ว่าจะมีข้อ จำกัด เล็กน้อยในหลาย ๆ แอพ +1 สำหรับคุณ
Antti Rytsölä

2

นี่คือวิธีที่ฉันทำ ...

1. สร้างสคริปต์ขนาดเล็กเพื่อทดสอบว่าไฟล์เป็นข้อความธรรมดาหรือไม่:

#!/bin/bash
[[ "$(file -bi $1)" == *"file"* ]]

2. ใช้ find เหมือนเดิม

find . -type f -exec istext {} \; -exec grep -nHi mystring {} \;

ฉันเดาว่าคุณหมายถึง== *"text"* ]]?
ผู้ใช้ไม่ทราบ

คุณอาจใช้ตัวดำเนินการจับคู่ `= ~" text "]]` แทน
ผู้ใช้ไม่ทราบ

2

ฉันมีปัญหาสองประการเกี่ยวกับคำตอบของประวัติศาสตร์:

  • แสดงเฉพาะไฟล์ข้อความ มันไม่ได้ค้นหาตามที่ร้องขอ ในการค้นหาจริงให้ใช้

    find . -type f -exec grep -Iq . {} \; -and -print0 | xargs -0 grep "needle text"
    
  • มันสร้างกระบวนการ grep สำหรับทุกไฟล์ซึ่งช้ามาก ทางออกที่ดีกว่าคือ

    find . -type f -print0 | xargs -0 grep -IZl . | xargs -0 grep "needle text"
    

    หรือเพียงแค่

    find . -type f -print0 | xargs -0 grep -I "needle text"
    

    นี้ใช้เวลาเพียง 0.2s เมื่อเทียบกับ 4s สำหรับการแก้ปัญหาดังกล่าวข้างต้น (2.5GB ข้อมูล / 7700 ไฟล์) คือ20x ได้เร็วขึ้น

นอกจากนี้ไม่มีใครอ้างถึงทางเลือกag, Silver Searcherหรือack-grep ¸as หากมีอย่างใดอย่างหนึ่งเป็นทางเลือกที่ดีกว่ามาก:

ag -t "needle text"    # Much faster than ack
ack -t "needle text"   # or ack-grep

หมายเหตุสุดท้ายโปรดระวังผลบวกที่ผิดพลาด (ไฟล์ไบนารีที่ถ่ายเป็นไฟล์ข้อความ) ฉันมีผลบวกเท็จโดยใช้ grep / ag / ack แล้วดังนั้นควรแสดงรายการไฟล์ที่ตรงกันก่อนที่จะแก้ไขไฟล์


1

แม้ว่าจะเป็นคำถามเก่า แต่ฉันคิดว่าข้อมูลนี้จะช่วยเพิ่มคุณภาพของคำตอบที่นี่

เมื่อละเว้นไฟล์ด้วยชุดบิตที่เรียกใช้งานได้ฉันเพียงแค่ใช้คำสั่งนี้:

find . ! -perm -111

เพื่อป้องกันไม่ให้ป้อนซ้ำในไดเรกทอรีอื่น:

find . -maxdepth 1 ! -perm -111

ไม่จำเป็นต้องไปป์เพื่อผสมคำสั่งมากมายเพียงแค่คำสั่งค้นหาธรรมดาที่ทรงพลัง

  • Disclaimer: มันไม่ได้ว่าสิ่งที่ OP ถามเพราะมันไม่ได้ตรวจสอบว่าไฟล์เป็นไบนารีหรือไม่ ตัวอย่างเช่นจะกรองไฟล์bash scriptซึ่งเป็นข้อความแต่มีชุดบิตที่เรียกใช้งานได้

ที่กล่าวมาฉันหวังว่านี่จะเป็นประโยชน์กับทุกคน


0

ฉันทำวิธีนี้: 1) เนื่องจากมีไฟล์มากเกินไป (~ 30k) ในการค้นหาผ่านฉันจึงสร้างรายการไฟล์ข้อความทุกวันเพื่อใช้ผ่าน crontab โดยใช้คำสั่งด้านล่าง:

find /to/src/folder -type f -exec file {} \; | grep text | cut -d: -f1 > ~/.src_list &

2) สร้างฟังก์ชันใน. bashrc:

findex() {
    cat ~/.src_list | xargs grep "$*" 2>/dev/null
}

จากนั้นฉันสามารถใช้คำสั่งด้านล่างเพื่อทำการค้นหา:

findex "needle text"

HTH :)


0

ฉันชอบ xargs

find . -type f | xargs grep -I "needle text"

หากชื่อไฟล์ของคุณดูแปลก ๆ โดยใช้ตัวเลือก -0:

find . -type f -print0 | xargs -0 grep -I "needle text"

0
  • ตัวอย่าง bash เพื่อค้นหาข้อความ "eth0" ใน / etc ในไฟล์ text / ascii ทั้งหมด

grep eth0 $ (ค้นหาไฟล์ / etc / -type f -exec {} \; | egrep -i "text | ascii" | cut -d ':' -f1)


0

นี่คือเวอร์ชันที่เรียบง่ายพร้อมคำอธิบายเพิ่มเติมสำหรับผู้เริ่มต้นเช่นฉันที่พยายามเรียนรู้วิธีใส่คำสั่งมากกว่าหนึ่งคำในหนึ่งบรรทัด

หากคุณเขียนปัญหาเป็นขั้นตอนจะมีลักษณะดังนี้:

// For every file in this directory
// Check the filetype
// If it's an ASCII file, then print out the filename

เพื่อให้บรรลุนี้เราสามารถใช้คำสั่งสามยูนิกซ์: find, และfilegrep

find จะตรวจสอบทุกไฟล์ในไดเร็กทอรี

fileจะให้ประเภทไฟล์แก่เรา ในกรณีของเราเรากำลังมองหาการส่งคืน "ข้อความ ASCII"

grep จะมองหาคำหลัก 'ASCII' ในผลลัพธ์จาก file

แล้วเราจะรวมสิ่งเหล่านี้เข้าด้วยกันเป็นบรรทัดเดียวได้อย่างไร? มีหลายวิธีในการทำ แต่ฉันพบว่าการทำตามลำดับรหัสหลอกของเรานั้นเหมาะสมที่สุด (โดยเฉพาะกับผู้เริ่มต้นอย่างฉัน)

find ./ -exec file {} ";" | grep 'ASCII'

ดูซับซ้อน แต่ก็ไม่เลวเมื่อเราทำลายมันลง:

find ./= ดูทุกไฟล์ในไดเร็กทอรีนี้ findคำสั่งพิมพ์ออกมาชื่อไฟล์ของไฟล์ใด ๆ ที่ตรงกับ 'การแสดงออก' หรืออะไรก็ตามมาหลังจากเส้นทางซึ่งในกรณีของเราคือไดเรกทอรีปัจจุบันหรือ./

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

-exec= แฟล็กนี้เป็นตัวเลือกภายในคำสั่ง find ที่อนุญาตให้เราใช้ผลลัพธ์ของคำสั่งอื่นเป็นนิพจน์การค้นหา มันเหมือนกับการเรียกใช้ฟังก์ชันภายในฟังก์ชัน

file {}= คำสั่งที่ถูกเรียกภายในfind. fileคำสั่งกลับสตริงที่จะบอกคุณ filetype ของไฟล์ที่ โดยปกติจะมีลักษณะดังนี้: file mytextfile.txt. ในกรณีของเราเราต้องการให้ใช้ไฟล์ใดก็ตามที่findคำสั่งดูอยู่ดังนั้นเราจึงใส่วงเล็บปีกกา{}เพื่อทำหน้าที่เป็นตัวแปรว่างหรือพารามิเตอร์ กล่าวอีกนัยหนึ่งเราแค่ขอให้ระบบส่งออกสตริงสำหรับทุกไฟล์ในไดเร็กทอรี

";"= สิ่งนี้จำเป็นโดยfindและเป็นเครื่องหมายวรรคตอนที่ท้าย-execคำสั่งของเรา ดูคู่มือสำหรับ 'พบ' man findสำหรับคำอธิบายมากขึ้นถ้าคุณจำเป็นต้องใช้มันโดยการเรียกใช้

| grep 'ASCII'= |เป็นท่อ ไปป์เอาเอาต์พุตของสิ่งที่อยู่ทางซ้ายและใช้เป็นอินพุตของสิ่งที่อยู่ทางขวา ใช้เอาต์พุตของfindคำสั่ง (สตริงที่เป็นประเภทไฟล์ของไฟล์เดียว) และทดสอบเพื่อดูว่ามีสตริง'ASCII'หรือไม่ ถ้าเป็นเช่นนั้นจะส่งคืนจริง

ตอนนี้นิพจน์ทางด้านขวาของfind ./จะคืนค่าจริงเมื่อgrepคำสั่งส่งคืนจริง Voila


0

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

$ # Let's make some test files
$ mkdir ASCII-finder
$ cd ASCII-finder
$ dd if=/dev/urandom of=binary.file bs=1M count=1
1+0 records in
1+0 records out
1048576 bytes (1.0 MB, 1.0 MiB) copied, 0.009023 s, 116 MB/s
$ file binary.file
binary.file: data
$ echo 123 > text.txt
$ # Let the magic begin
$ find -type f -print0 | \
    xargs -0 -I @@ bash -c 'file "$@" | grep ASCII &>/dev/null && echo "file is ASCII: $@"' -- @@

เอาท์พุต:

file is ASCII: ./text.txt

Legend: $เป็นเชลล์พร้อมต์แบบโต้ตอบที่เราป้อนคำสั่งของเรา

คุณสามารถแก้ไขส่วนหลังได้ &&เพื่อเรียกสคริปต์อื่น ๆ หรือทำสิ่งอื่น ๆ แบบอินไลน์ได้เช่นกันเช่นหากไฟล์นั้นมีสตริงที่กำหนดให้ cat ทั้งไฟล์หรือมองหาสตริงรองในนั้น

คำอธิบาย:

  • find รายการที่เป็นไฟล์
  • ทำให้xargsฟีดแต่ละรายการเป็นบรรทัดเป็นหนึ่งbash คำสั่งซับ/ สคริปต์
  • fileตรวจสอบประเภทของไฟล์โดย magic byte grepตรวจสอบว่ามี ASCII อยู่หรือไม่หากเป็นเช่นนั้นหลังจากนั้น&&คำสั่งถัดไปของคุณจะรัน
  • findพิมพ์ผลลัพธ์nullแยกออกจากกันซึ่งเป็นการดีที่จะหลีกเลี่ยงชื่อไฟล์ที่มีช่องว่างและอักขระเมตา
  • xargsโดยใช้-0ตัวเลือกอ่านnullแยกกัน-I @@ รับแต่ละเร็กคอร์ดและใช้เป็นพารามิเตอร์ตำแหน่ง / args ไปยัง bash script
  • --เพื่อbashให้แน่ใจว่าสิ่งที่เกิดขึ้นหลังจากนั้นเป็นอาร์กิวเมนต์แม้ว่าจะเริ่มต้นด้วย-like -cซึ่งอาจตีความได้ว่าเป็นตัวเลือก bash

หากคุณต้องการค้นหาประเภทอื่นที่ไม่ใช่ ASCII ให้แทนที่grep ASCIIด้วยประเภทอื่นเช่นgrep "PDF document, version 1.4"


-1
find . -type f | xargs file | grep "ASCII text" | awk -F: '{print $1}'

ใช้คำสั่ง find เพื่อแสดงรายการไฟล์ทั้งหมดใช้คำสั่ง file เพื่อตรวจสอบว่าเป็นข้อความ (ไม่ใช่ tar, key) สุดท้ายใช้คำสั่ง awk เพื่อกรองและพิมพ์ผลลัพธ์


-4

เกี่ยวกับเรื่องนี้

 find . -type f|xargs grep "needle text"

สิ่งนี้ไม่ได้มองหา"needle text"
peoro

@Navi: ตัวอย่าง OP ที่ให้มาจะพบเฉพาะไฟล์ที่มี"needl text"
peoro

3
@Navi: ตอนนี้มันไม่ได้มองหาไฟล์ข้อความอีกต่อไป: ถ้าไฟล์ไบนารีมี"needle text"อยู่จะพบ
peoro

ทำไมฉันถึงฟังคุณ
Navi

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