Thực thi lệnh trên tất cả các tệp trong một thư mục


290

Ai đó có thể vui lòng cung cấp mã để làm như sau: Giả sử có một thư mục của các tệp, tất cả đều cần được chạy qua một chương trình. Chương trình đưa ra kết quả để chuẩn ra. Tôi cần một tập lệnh sẽ đi vào một thư mục, thực thi lệnh trên mỗi tệp và nối đầu ra thành một tệp đầu ra lớn.

Chẳng hạn, để chạy lệnh trên 1 tệp:

$ cmd [option] [filename] > results.out

3
Tôi muốn thêm vào câu hỏi. Nó có thể được thực hiện bằng cách sử dụng xargs? ví dụ: ls <directory> | xargs cmd [options] {filenames put in here automatically by xargs} [more arguments] > results.out
Ozair Kafray

2
Nó có thể, nhưng có lẽ bạn không muốn sử dụngls để lái xe xargs. Nếu hoàn cmdtoàn được viết, có lẽ bạn chỉ cần làm cmd <wildcard>.
tripleee

Câu trả lời:


425

Mã bash sau đây sẽ truyền tệp $ vào lệnh trong đó tệp $ sẽ đại diện cho mọi tệp trong / dir

for file in /dir/*
do
  cmd [option] "$file" >> results.out
done

Thí dụ

el@defiant ~/foo $ touch foo.txt bar.txt baz.txt
el@defiant ~/foo $ for i in *.txt; do echo "hello $i"; done
hello bar.txt
hello baz.txt
hello foo.txt

23
Nếu không có tệp nào tồn tại /dir/, thì vòng lặp vẫn chạy một lần với giá trị '*' cho $file, điều này có thể không mong muốn. Để tránh điều này, hãy bật nullglob trong suốt thời gian của vòng lặp. Thêm dòng này trước vòng lặp shopt -s nullglobvà dòng này sau vòng lặp shopt -u nullglob #revert nullglob back to it's normal default state.
Stew-au

43
+1, và nó chỉ tốn của tôi toàn bộ bộ sưu tập hình nền của tôi. tất cả mọi người sau tôi, sử dụng doublequote. "$ file"
Behrooz

Nếu tệp đầu ra giống nhau bên trong vòng lặp, thì việc chuyển hướng bên ngoài vòng lặp sẽ hiệu quả hơn nhiều done >results.out(và có lẽ sau đó bạn có thể ghi đè thay vì nối thêm, như tôi đã giả định ở đây).
tripleee

Làm thế nào để bạn có được các tệp kết quả riêng được đặt tên tùy chỉnh cho các tệp đầu vào của họ?
Timothy Swan

1
hãy cẩn thận bằng cách sử dụng lệnh này cho số lượng lớn tệp trong thư mục. Sử dụng find -exec thay thế.
kolisko

181

Còn cái này thì sao:

find /some/directory -maxdepth 1 -type f -exec cmd option {} \; > results.out
  • -maxdepth 1đối số ngăn tìm thấy từ đệ quy giảm dần vào bất kỳ thư mục con nào. (Nếu bạn muốn các thư mục lồng nhau như vậy được xử lý, bạn có thể bỏ qua phần này.)
  • -type -f xác định rằng chỉ các tệp đơn giản sẽ được xử lý.
  • -exec cmd option {}bảo nó chạy cmdvới chỉ định optioncho từng tệp được tìm thấy, với tên tệp được thay thế cho{}
  • \; biểu thị sự kết thúc của lệnh.
  • Cuối cùng, đầu ra từ tất cả các lần cmdthực hiện riêng lẻ được chuyển hướng đến results.out

Tuy nhiên, nếu bạn quan tâm đến thứ tự các tệp được xử lý, bạn nên viết một vòng lặp. Tôi nghĩ rằng findxử lý các tệp theo thứ tự inode (mặc dù tôi có thể sai về điều đó), có thể không phải là những gì bạn muốn.


1
Đây là cách chính xác để xử lý tập tin. Sử dụng vòng lặp for dễ bị lỗi do nhiều lý do. Ngoài ra việc sắp xếp có thể được thực hiện bằng cách sử dụng các lệnh khác như stat, và sortkhóa học này phụ thuộc vào tiêu chí sắp xếp là gì.
tuxdna

1
Nếu tôi muốn chạy hai lệnh, làm thế nào để tôi liên kết chúng sau -exectùy chọn? Tôi có phải bọc chúng trong dấu ngoặc đơn hay cái gì đó không?
frei

findluôn là tùy chọn tốt nhất vì bạn có thể lọc theo mẫu tên tệp với tùy chọn -namevà bạn có thể thực hiện trong một lệnh duy nhất.
João Pimentel Ferreira

3
@frei câu trả lời cho câu hỏi của bạn là đây: stackoverflow.com/a/6043896/1243247 nhưng về cơ bản chỉ cần thêm -exectùy chọn:find . -name "*.txt" -exec echo {} \; -exec grep banana {} \;
João Pimentel Ferreira

2
Làm thế nào bạn có thể tham chiếu tên tập tin như là tùy chọn?
Toskan

54

Tôi đang làm điều này trên pi mâm xôi của tôi từ dòng lệnh bằng cách chạy:

for i in *;do omxplayer "$i";done

7

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 bashvỏ 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 ( nullglobbashcụ 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 continuechúng tôi sẽ quay trở lại forvò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ì bashbên trong sử dụng các Cchuỗ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 printftùy chọn shell để phân định các tệp có byte NULL này bằng cách sử dụng -dtùy chọn readlệ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 nullglobprintfđượ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 nullglobtù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 readlệnh là không POSIX compliant, vì vậy cần có một bashvỏ cho điều này được thực hiện. Sử dụng findlệ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 findtriể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ó. findPhiê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

1
+1 để kiểm tra xem tệp có tồn tại không. Nếu tìm kiếm trong một thư mục không tồn tại, tệp $ chứa chuỗi regex "/ invald_dir / *" không phải là tên tệp hợp lệ.
cdalxndr

3

Một cách nhanh chóng và bẩn thỉu đôi khi hoàn thành công việc là:

find directory/ | xargs  Command 

Ví dụ: để tìm số dòng trong tất cả các tệp trong thư mục hiện tại, bạn có thể làm:

find . | xargs wc -l

8
@Hubert Tại sao bạn có dòng mới trong tên tệp của bạn?!
musicin3d

2
đó không phải là câu hỏi "tại sao", đó là câu hỏi về tính chính xác - tên tệp không phải bao gồm các ký tự có thể in được, chúng thậm chí không phải là chuỗi UTF-8 hợp lệ. Ngoài ra, những gì một dòng mới phụ thuộc rất nhiều vào mã hóa, một mã hóa là dòng mới của một dòng khác. Xem mã trang 437
Hubert Kario

2
cmon, thật sao? việc này không hiệu quả 99,9% thời gian và anh ta đã nói "nhanh và bẩn"
Edoardo

Tôi không phải là fan hâm mộ của các kịch bản Bash "nhanh và bẩn" (AKA "bị hỏng"). Sớm muộn gì nó cũng kết thúc bằng những thứ như "Moved ~/.local/share/steam. Ran steam. Nó đã xóa mọi thứ trên hệ thống do người dùng sở hữu." báo cáo lỗi.
giảm hoạt động

Điều này cũng sẽ không hoạt động với các tệp có không gian trong tên.
Shamas S - Tái lập Monica

2

Tôi cần phải sao chép tất cả các tệp .md từ thư mục này sang thư mục khác, vì vậy đây là những gì tôi đã làm.

for i in **/*.md;do mkdir -p ../docs/"$i" && rm -r ../docs/"$i" && cp "$i" "../docs/$i" && echo "$i -> ../docs/$i"; done

Điều này khá khó đọc, vì vậy hãy phá vỡ nó.

cd đầu tiên vào thư mục với các tập tin của bạn,

for i in **/*.md; cho mỗi tệp trong mẫu của bạn

mkdir -p ../docs/"$i"làm cho thư mục đó trong một thư mục tài liệu bên ngoài thư mục chứa các tệp của bạn. Mà tạo một thư mục bổ sung có cùng tên với tập tin đó.

rm -r ../docs/"$i" xóa thư mục bổ sung được tạo như là kết quả của mkdir -p

cp "$i" "../docs/$i" Sao chép tập tin thực tế

echo "$i -> ../docs/$i" Báo lại những gì bạn đã làm

; done Sống hạnh phúc mãi mãi về sau


Lưu ý: **để hoạt động, globstartùy chọn shell cần được đặt:shopt -s globstar
Hubert Kario

2

Bạn có thể dùng xarg

ls | xargs -L 1 -d '\n' your-desired-command

-L 1 gây ra 1 mục tại một thời điểm

-d '\n'làm cho đầu ra của lsđược phân chia dựa trên dòng mới.


1

Dựa trên phương pháp của @Jim Lewis:

Đây là một giải pháp nhanh chóng bằng cách sử dụng findvà sắp xếp các tệp theo ngày sửa đổi của chúng:

$ find  directory/ -maxdepth 1 -type f -print0 | \
  xargs -r0 stat -c "%y %n" | \
  sort | cut -d' ' -f4- | \
  xargs -d "\n" -I{} cmd -op1 {} 

Để sắp xếp xem:

http://www.commandlinefu.com/commands/view/5720/find-files-and-list-them-sort-by-modification-time


điều này sẽ không hoạt động nếu các tệp có dòng mới trong tên của chúng
Hubert Kario

1
@HubertKario Bạn có thể muốn đọc thêm về -print0cho find-0cho xargsđó sử dụng ký tự null thay vì bất kỳ khoảng trắng (bao gồm cả dòng mới).
tuxdna

vâng, sử dụng -print0là một cái gì đó có ích, nhưng toàn bộ đường ống cần sử dụng một cái gì đó như thế này, và sortkhông phải
Hubert Kario

1

tôi nghĩ giải pháp đơn giản là:

sh /dir/* > ./result.txt

2
Bạn đã hiểu đúng câu hỏi chưa? Điều này sẽ chỉ cố gắng chạy từng tệp trong thư mục thông qua shell - như thể nó là một tập lệnh.
RDAs

1

Tối đa

Tôi thấy nó hoạt động độc đáo với câu trả lời của Jim Lewis chỉ cần thêm một chút như thế này:

$ export DIR=/path/dir && cd $DIR && chmod -R +x *
$ find . -maxdepth 1 -type f -name '*.sh' -exec {} \; > results.out

Thứ tự sắp xếp

Nếu bạn muốn thực hiện theo thứ tự sắp xếp, sửa đổi nó như thế này:

$ export DIR=/path/dir && cd $DIR && chmod -R +x *
find . -maxdepth 2 -type f -name '*.sh' | sort | bash > results.out

Chỉ cần một ví dụ, điều này sẽ thực hiện theo thứ tự sau:

bash: 1: ./assets/main.sh
bash: 2: ./builder/clean.sh
bash: 3: ./builder/concept/compose.sh
bash: 4: ./builder/concept/market.sh
bash: 5: ./builder/concept/services.sh
bash: 6: ./builder/curl.sh
bash: 7: ./builder/identity.sh
bash: 8: ./concept/compose.sh
bash: 9: ./concept/market.sh
bash: 10: ./concept/services.sh
bash: 11: ./product/compose.sh
bash: 12: ./product/market.sh
bash: 13: ./product/services.sh
bash: 14: ./xferlog.sh

Độ sâu không giới hạn

Nếu bạn muốn thực hiện ở độ sâu không giới hạn theo điều kiện nhất định, bạn có thể sử dụng điều này:

export DIR=/path/dir && cd $DIR && chmod -R +x *
find . -type f -name '*.sh' | sort | bash > results.out

sau đó đặt lên trên mỗi tệp trong các thư mục con như thế này:

#!/bin/bash
[[ "$(dirname `pwd`)" == $DIR ]] && echo "Executing `realpath $0`.." || return

và một nơi nào đó trong phần thân của tệp cha mẹ:

if <a condition is matched>
then
    #execute child files
    export DIR=`pwd`
fi
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.