Các câu trả lời được chấp nhận / được bình chọn cao là rất tốt, nhưng chúng đang thiếu một vài chi tiết khó chịu. Bài đăng này trình bày các trường hợp về cách xử lý tốt hơn khi mở rộng tên đường dẫn vỏ (global) không thành công, khi tên tệp chứa các dòng mới / biểu tượng dấu gạch ngang và di chuyển đầu ra lệnh ra khỏi vòng lặp for khi ghi kết quả vào tập tin.
Khi chạy mở rộng toàn cầu shell bằng cách sử dụng, *
có khả năng mở rộng không thành công nếu không có tệp nào trong thư mục và chuỗi toàn cầu không được mở rộng sẽ được chuyển đến lệnh để chạy, có thể có kết quả không mong muốn. Các bash
vỏ cung cấp một tùy chọn vỏ mở rộng cho việc sử dụng này nullglob
. Vì vậy, vòng lặp về cơ bản trở thành như sau trong thư mục chứa các tệp của bạn
shopt -s nullglob
for file in ./*; do
cmdToRun [option] -- "$file"
done
Điều này cho phép bạn thoát khỏi vòng lặp for một cách an toàn khi biểu thức ./*
không trả về bất kỳ tệp nào (nếu thư mục trống)
hoặc theo một cách phù hợp POSIX ( nullglob
là bash
cụ thể)
for file in ./*; do
[ -f "$file" ] || continue
cmdToRun [option] -- "$file"
done
Điều này cho phép bạn vào bên trong vòng lặp khi biểu thức không thành công một lần và [ -f "$file" ]
kiểm tra điều kiện nếu chuỗi không được mở rộng ./*
là tên tệp hợp lệ trong thư mục đó, điều này sẽ không xảy ra. Vì vậy, với điều kiện thất bại này, sử dụng continue
chúng tôi sẽ quay trở lại for
vòng lặp sẽ không chạy sau đó.
Cũng lưu ý việc sử dụng --
ngay trước khi truyền đối số tên tệp. Điều này là cần thiết bởi vì như đã lưu ý trước đó, tên tệp shell có thể chứa dấu gạch ngang ở bất cứ đâu trong tên tệp. Một số lệnh shell diễn giải điều đó và coi chúng như một tùy chọn lệnh khi tên không được trích dẫn chính xác và thực thi suy nghĩ lệnh nếu cờ được cung cấp.
Các --
tín hiệu kết thúc các tùy chọn dòng lệnh trong trường hợp đó có nghĩa là, lệnh không nên phân tích bất kỳ chuỗi nào ngoài điểm này dưới dạng cờ lệnh mà chỉ là tên tệp.
Trích dẫn hai tên tệp giải quyết chính xác các trường hợp khi tên chứa ký tự toàn cục hoặc khoảng trắng. Nhưng tên tệp * nix cũng có thể chứa dòng mới trong đó. Vì vậy, chúng tôi giới hạn tên tệp với ký tự duy nhất không thể là một phần của tên tệp hợp lệ - byte byte ( \0
). Vì bash
bên trong sử dụng các C
chuỗi kiểu trong đó các byte null được sử dụng để chỉ ra phần cuối của chuỗi, nên đây là ứng cử viên phù hợp cho việc này.
Vì vậy, bằng cách sử dụng printf
tùy chọn shell để phân định các tệp có byte NULL này bằng cách sử dụng -d
tùy chọn read
lệnh, chúng ta có thể thực hiện bên dưới
( shopt -s nullglob; printf '%s\0' ./* ) | while read -rd '' file; do
cmdToRun [option] -- "$file"
done
Cái nullglob
và printf
được bao bọc xung quanh (..)
có nghĩa là về cơ bản chúng được chạy trong một vỏ con (vỏ con), vì để tránh nullglob
tùy chọn phản chiếu trên vỏ cha, một khi lệnh thoát. Các -d ''
tùy chọn của read
lệnh là không POSIX compliant, vì vậy cần có một bash
vỏ cho điều này được thực hiện. Sử dụng find
lệnh này có thể được thực hiện như là
while IFS= read -r -d '' file; do
cmdToRun [option] -- "$file"
done < <(find -maxdepth 1 -type f -print0)
Đối với các find
triển khai không hỗ trợ -print0
(ngoài triển khai GNU và FreeBSD), điều này có thể được mô phỏng bằng cách sử dụngprintf
find . -maxdepth 1 -type f -exec printf '%s\0' {} \; | xargs -0 cmdToRun [option] --
Một cách khắc phục quan trọng khác là di chuyển hướng ra khỏi vòng lặp for để giảm số lượng I / O tệp cao. Khi được sử dụng bên trong vòng lặp, shell phải thực hiện các cuộc gọi hệ thống hai lần cho mỗi lần lặp của vòng lặp for, một lần để mở và một lần để đóng bộ mô tả tệp được liên kết với tệp. Điều này sẽ trở thành một cổ chai trên hiệu suất của bạn để chạy các vòng lặp lớn. Đề xuất đề xuất sẽ là di chuyển nó ra ngoài vòng lặp.
Mở rộng mã trên với bản sửa lỗi này, bạn có thể làm
( shopt -s nullglob; printf '%s\0' ./* ) | while read -rd '' file; do
cmdToRun [option] -- "$file"
done > results.out
về cơ bản sẽ đưa nội dung lệnh của bạn cho mỗi lần lặp đầu vào tệp của bạn vào thiết bị xuất chuẩn và khi vòng lặp kết thúc, hãy mở tệp mục tiêu một lần để ghi nội dung của thiết bị xuất chuẩn và lưu nó. find
Phiên bản tương đương giống nhau sẽ là
while IFS= read -r -d '' file; do
cmdToRun [option] -- "$file"
done < <(find -maxdepth 1 -type f -print0) > results.out
ls <directory> | xargs cmd [options] {filenames put in here automatically by xargs} [more arguments] > results.out