Có một số cách khả thi để thực hiện điều này.
Nếu bạn muốn bám sát phiên bản gốc của mình, bạn có thể thực hiện theo cách này:
getlist() {
IFS=$'\n'
for file in $(find . -iname 'foo*') ; do
printf 'File found: %s\n' "$file"
done
}
Điều này sẽ vẫn thất bại nếu tên tệp có dòng mới theo nghĩa đen, nhưng không gian sẽ không phá vỡ nó.
Tuy nhiên, gây rối với IFS là không cần thiết. Đây là cách ưa thích của tôi để làm điều này:
getlist() {
while IFS= read -d $'\0' -r file ; do
printf 'File found: %s\n' "$file"
done < <(find . -iname 'foo*' -print0)
}
Nếu bạn thấy < <(command)
cú pháp không quen thuộc, bạn nên đọc về quá trình thay thế . Ưu điểm của việc này for file in $(find ...)
là các tệp có dấu cách, dòng mới và các ký tự khác được xử lý chính xác. Điều này hoạt động vì find
với -print0
sẽ sử dụng null
(aka \0
) làm dấu kết thúc cho mỗi tên tệp và, không giống như dòng mới, null không phải là ký tự hợp pháp trong tên tệp.
Lợi thế này so với phiên bản gần tương đương
getlist() {
find . -iname 'foo*' -print0 | while read -d $'\0' -r file ; do
printf 'File found: %s\n' "$file"
done
}
Có phải bất kỳ phép gán biến trong phần thân của vòng lặp while đều được giữ nguyên. Đó là, nếu bạn đặt ống while
như trên thì phần thân của while
nó nằm trong một khung con có thể không phải là thứ bạn muốn.
Ưu điểm của phiên bản thay thế quá trình find ... -print0 | xargs -0
là tối thiểu: xargs
Phiên bản vẫn ổn nếu tất cả những gì bạn cần là in một dòng hoặc thực hiện một thao tác trên tệp, nhưng nếu bạn cần thực hiện nhiều bước thì phiên bản vòng lặp sẽ dễ dàng hơn.
EDIT : Đây là một kịch bản thử nghiệm hay để bạn có thể biết được sự khác biệt giữa các nỗ lực khác nhau trong việc giải quyết vấn đề này
#!/usr/bin/env bash
dir=/tmp/getlist.test/
mkdir -p "$dir"
cd "$dir"
touch 'file not starting foo' foo foobar barfoo 'foo with spaces'\
'foo with'$'\n'newline 'foo with trailing whitespace '
# while with process substitution, null terminated, empty IFS
getlist0() {
while IFS= read -d $'\0' -r file ; do
printf 'File found: '"'%s'"'\n' "$file"
done < <(find . -iname 'foo*' -print0)
}
# while with process substitution, null terminated, default IFS
getlist1() {
while read -d $'\0' -r file ; do
printf 'File found: '"'%s'"'\n' "$file"
done < <(find . -iname 'foo*' -print0)
}
# pipe to while, newline terminated
getlist2() {
find . -iname 'foo*' | while read -r file ; do
printf 'File found: '"'%s'"'\n' "$file"
done
}
# pipe to while, null terminated
getlist3() {
find . -iname 'foo*' -print0 | while read -d $'\0' -r file ; do
printf 'File found: '"'%s'"'\n' "$file"
done
}
# for loop over subshell results, newline terminated, default IFS
getlist4() {
for file in "$(find . -iname 'foo*')" ; do
printf 'File found: '"'%s'"'\n' "$file"
done
}
# for loop over subshell results, newline terminated, newline IFS
getlist5() {
IFS=$'\n'
for file in $(find . -iname 'foo*') ; do
printf 'File found: '"'%s'"'\n' "$file"
done
}
# see how they run
for n in {0..5} ; do
printf '\n\ngetlist%d:\n' $n
eval getlist$n
done
rm -rf "$dir"