Làm cách nào để phân tích đầu ra của lệnh find khi tên tệp có khoảng trắng trong chúng?


12

Sử dụng một vòng lặp như

for i in `find . -name \*.txt` 

sẽ phá vỡ nếu một số tên tệp có khoảng trắng trong đó.

Tôi có thể sử dụng kỹ thuật gì để tránh vấn đề này?


1
Lưu ý rằng các tệp cũng có thể có dòng mới trong tên tệp của chúng. Đó là lý do tại sao có find -print0xargs -0.
Daniel Beck

Câu trả lời:


12

Lý tưởng nhất là bạn không làm theo cách đó, bởi vì phân tích tên tệp đúng trong tập lệnh shell luôn khó khăn (sửa nó cho khoảng trắng, bạn vẫn sẽ gặp vấn đề với các ký tự nhúng khác, đặc biệt là dòng mới). Điều này thậm chí còn được liệt kê là mục đầu tiên trong trang BashPit thác.

Điều đó nói rằng, có một cách để gần như làm những gì bạn muốn:

oIFS=$IFS
IFS=$'\n'

find . -name '*.txt' | while read -r i; do
  # use "$i" with whatever you're doing
done

IFS=$oIFS

Hãy nhớ trích dẫn $ikhi sử dụng nó, để tránh những thứ khác diễn giải các khoảng trắng sau này. Cũng nhớ đặt $IFSlại sau khi sử dụng, vì không làm như vậy sẽ gây ra lỗi hoang mang sau này.

Điều này có một cảnh báo khác được đính kèm: những gì xảy ra bên trong whilevòng lặp có thể diễn ra trong một lớp con, tùy thuộc vào lớp vỏ chính xác mà bạn đang sử dụng, vì vậy các cài đặt biến có thể không tồn tại. Các fortránh phiên bản loop đó nhưng với mức giá đó, thậm chí nếu bạn áp dụng các $IFSgiải pháp cho các vấn đề tránh với không gian, sau đó bạn sẽ gặp rắc rối nếu findlợi nhuận quá nhiều file.

Tại một số điểm, bản sửa lỗi chính xác cho tất cả những điều này trở thành thực hiện bằng ngôn ngữ như Perl hoặc Python thay vì shell.


1
Tôi thích ý tưởng chỉ sử dụng Python để tránh tất cả điều này.
Scott C Wilson

12

Sử dụng find -print0và dẫn nó đến xargs -0, hoặc viết chương trình C nhỏ của riêng bạn và chuyển nó sang chương trình C nhỏ của bạn. Đây là những gì -print0-0đã được phát minh ra.

Các kịch bản Shell không phải là cách tốt nhất để xử lý tên tệp có khoảng trắng trong đó: bạn có thể làm điều đó, nhưng nó trở nên lộn xộn.


Hoạt động trên máy của tôi ^ TM!
mcandre

2

Bạn có thể đặt "dấu tách trường nội bộ" ( IFS) thành một thứ khác ngoài không gian để phân tách đối số vòng lặp, vd

ORIGIFS=${IFS}
NL='
'
IFS=${NL}
for i in $(find . -name '*.txt'); do
    IFS=${ORIGIFS}
    #do stuff
done
IFS=${ORIGIFS}

Tôi thiết lập lại IFSsau khi sử dụng nó trong tìm kiếm, chủ yếu là vì nó trông đẹp, tôi nghĩ vậy. Tôi chưa thấy bất kỳ vấn đề nào trong việc đặt nó thành dòng mới, nhưng tôi nghĩ rằng đây là "sạch hơn".

Một phương pháp khác, tùy thuộc vào những gì bạn muốn làm với đầu ra từ find, là sử dụng trực tiếp -execvới findlệnh hoặc sử dụng -print0và đưa nó vào xargs -0. Trong trường hợp đầu tiên find, chăm sóc tên tập tin thoát. Trong -print0trường hợp, findin đầu ra của nó bằng một dấu tách null, và sau đó xargsphân tách trên này. Vì không có tên tệp nào có thể chứa ký tự đó (những gì tôi biết), nên điều này luôn an toàn. Điều này chủ yếu hữu ích trong các trường hợp đơn giản; và thường không phải là một thay thế tuyệt vời cho một forvòng lặp đầy đủ .


1

Sử dụng find -print0vớixargs -0

Sử dụng find -print0kết hợp với xargs -0hoàn toàn mạnh mẽ đối với tên tệp hợp pháp và là một trong những phương pháp mở rộng nhất hiện có. Ví dụ: giả sử bạn muốn có một danh sách mọi tệp PDF trong thư mục hiện tại. Bạn có thể viết

$ find . -iname '*.pdf' -print0 | xargs -0 -n 1 echo

Điều này sẽ tìm thấy mọi PDF (thông qua -iname '*.pdf') trong thư mục hiện tại ( .) và bất kỳ thư mục con nào, và chuyển từng tệp đó làm đối số cho echolệnh. Bởi vì chúng tôi đã chỉ định -n 1tùy chọn, xargssẽ chỉ chuyển một đối số tại một thời điểm echo. Nếu chúng ta bỏ qua tùy chọn đó, xargssẽ vượt qua càng nhiều càng tốt echo. (Bạn có thể echo short input | xargs --show-limitsxem có bao nhiêu byte được cho phép trong một dòng lệnh.)

xargsChính xác thì làm gì?

Chúng ta có thể thấy rõ hiệu ứng xargscó trên đầu vào của nó - và -nđặc biệt là hiệu ứng - bằng cách sử dụng một tập lệnh lặp lại các đối số của nó theo cách chính xác hơn echo.

$ cat > echoArgs.sh <<'EOF'
#!/bin/bash
echo "Number of arguments: $#"

[[ $# -eq 0 ]] && exit

for i in $(seq 1 $#); do
    echo "Arg $i: <$1>"
    shift
done
EOF

$ find . -iname '*.pdf' -print0 | xargs -0 ./echoArgs.sh
$ find . -iname '*.pdf' -print0 | xargs -0 -n 1 ./echoArgs.sh

Lưu ý rằng nó xử lý không gian và dòng mới hoàn toàn tốt,

$ touch 'A space-age
new line of vending machines.pdf'
$ find . -iname '*space*' -print0 | xargs -0 -n 1 ./echoArgs.sh

mà sẽ đặc biệt rắc rối với giải pháp phổ biến sau:

chmod +x ./echoArgs.sh
for file in $(ls *spacey*); do
  ./echoArgs.sh "$file"
done
Ghi chú

1

Tôi không đồng ý với các bashbashers, bởi vì bash, cùng với bộ công cụ * nix, khá giỏi trong việc xử lý các tệp (bao gồm cả những cái có tên có khoảng trắng được nhúng).

Trên thực tế, findcung cấp cho bạn quyền kiểm soát hạt tốt trong việc chọn tệp nào sẽ xử lý ... Về phía bash, bạn thực sự chỉ cần nhận ra rằng bạn phải tạo chuỗi cho bạn bash words; thông thường bằng cách sử dụng "dấu ngoặc kép" hoặc một số cơ chế khác như sử dụng IFS hoặc find{}

Lưu ý rằng trong hầu hết / nhiều tình huống bạn không cần thiết lập và đặt lại IFS; chỉ sử dụng IFS cục bộ như trong các ví dụ dưới đây. Cả ba xử lý khoảng trắng đều ổn. Ngoài ra, bạn không cần một cấu trúc vòng lặp "tiêu chuẩn", bởi vì find thực sự \; một vòng lặp; chỉ cần đặt logic vòng lặp của bạn vào hàm bash (nếu bạn không gọi một công cụ tiêu chuẩn).

IFS=$'\n' find ~/ -name '*.txt' -exec  function-or-util {} \;  

Và, hai ví dụ nữa

IFS=$'\n' find ~/ -name '*.txt' -exec  printf 'Hello %s\n' {} \;  
IFS=$'\n' find ~/ -name '*.txt' -exec  echo {} \+ |sed 's/home//'  

'tìm also allows you to pass multiple filenames as args to you script ..(if it suits your need: use+ instead\; `)


1
Có một số giá trị cho cả hai quan điểm. Khi tôi chỉ làm việc với các tệp của riêng mình, tôi sẽ chỉ sử dụng find và không lo lắng về nó, vì các tệp của tôi không có khoảng trắng (hoặc trả về vận chuyển!) Trong tên của chúng. Nhưng khi bạn bắt đầu làm việc với các tệp của người khác, bạn phải sử dụng các kỹ thuật mạnh mẽ hơn.
Scott C Wilson
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.