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?
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?
Câu trả lời:
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 $i
khi 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 $IFS
lạ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 while
vò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 for
trá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 $IFS
giả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 find
lợ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.
Sử dụng find -print0
và 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
và -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.
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 IFS
sau 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 -exec
với find
lệnh hoặc sử dụng -print0
và đư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 -print0
trường hợp, find
in đầu ra của nó bằng một dấu tách null, và sau đó xargs
phâ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 for
vòng lặp đầy đủ .
find -print0
vớixargs -0
Sử dụng find -print0
kết hợp với xargs -0
hoà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 echo
lệnh. Bởi vì chúng tôi đã chỉ định -n 1
tùy chọn, xargs
sẽ 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 đó, xargs
sẽ vượt qua càng nhiều càng tốt echo
. (Bạn có thể echo short input | xargs --show-limits
xem có bao nhiêu byte được cho phép trong một dòng lệnh.)
xargs
Chính xác thì làm gì?Chúng ta có thể thấy rõ hiệu ứng xargs
có 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ú
Tôi không đồng ý với các bash
bashers, 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ế, find
cung 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ự \;
là 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
\; `)
find -print0
vàxargs -0
.