lặp qua `ls` dẫn đến tập lệnh bash shell


93

Có ai có một kịch bản shell shell để làm một cái gì đó với lsmột danh sách các tên thư mục và lặp qua từng cái và làm một cái gì đó không?

Tôi dự định làm ls -1d */để có được danh sách tên thư mục.

Câu trả lời:


110

Đã chỉnh sửa không sử dụng ls nơi mà một quả địa cầu sẽ làm, như @ shawn-j-goff và những người khác đề xuất.

Chỉ cần sử dụng một for..do..donevòng lặp:

for f in *; do
  echo "File -> $f"
done

Bạn có thể thay thế *bằng *.txthoặc bất kỳ quả cầu nào khác trả về danh sách (tệp, thư mục hoặc bất cứ thứ gì cho vấn đề đó), một lệnh tạo danh sách, ví dụ $(cat filelist.txt), hoặc thực sự thay thế nó bằng danh sách.

Trong dovòng lặp, bạn chỉ cần tham khảo biến vòng lặp với tiền tố ký hiệu đô la ( $ftrong ví dụ trên). Bạn có thể echonó hoặc làm bất cứ điều gì khác với nó bạn muốn.

Ví dụ: để đổi tên tất cả các .xmltệp trong thư mục hiện tại thành .txt:

for x in *.xml; do 
  t=$(echo $x | sed 's/\.xml$/.txt/'); 
  mv $x $t && echo "moved $x -> $t"
done

Hoặc thậm chí tốt hơn, nếu bạn đang sử dụng Bash, bạn có thể sử dụng các mở rộng tham số Bash thay vì sinh ra một lớp con:

for x in *.xml; do 
  t=${x%.xml}.txt
  mv $x $t && echo "moved $x -> $t"
done

22
Nếu tên tệp có một khoảng trắng trong đó thì sao?
Daniel A. Trắng

4
Thật không may, như Daniel đã nói, mã trong câu trả lời này sẽ bị hỏng nếu bất kỳ tệp hoặc thư mục nào chứa một khoảng trắng hoặc dòng mới trong tên của chúng. Nó cho thấy một sự lạm dụng rất phổ biến của forcác vòng lặp và một cạm bẫy điển hình của việc cố gắng phân tích đầu ra của ls. @ DanielA.White, bạn có thể xem xét không chấp nhận câu trả lời này nếu nó không hữu ích (hoặc có khả năng gây hiểu lầm), vì như bạn đã nói, bạn đang hành động trên các thư mục. Câu trả lời của Shawn J. Goff sẽ cung cấp một giải pháp hiệu quả và hiệu quả hơn cho vấn đề của bạn.
slhck

4
-∞ Bạn không nên phân tích lsđầu ra , bạn không nên đọc đầu ra trong một forvòng lặp , bạn nên sử dụng $()thay vì `` và Sử dụng thêm Báo giá ™ . Tôi chắc chắn @CoverosGene có nghĩa là tốt, nhưng điều này thật tồi tệ.
l0b0

1
Một thay thế tốt hơn nếu bạn thực sự muốn sử dụng lsls -1 | while read line; do stuff; done. Ít nhất cái đó sẽ không phá vỡ cho khoảng trắng.
ejoubaud

1
với mv -vbạn không cầnecho "moved $x -> $t"
DmitrySandalov

68

Sử dụng đầu ra của lsđể có được tên tệp là một ý tưởng tồi . Nó có thể dẫn đến các kịch bản trục trặc và thậm chí nguy hiểm. Điều này là do tên tệp có thể chứa bất kỳ ký tự nào ngoại trừ /nullký tự đó và lskhông sử dụng một trong hai ký tự đó làm dấu phân cách, vì vậy nếu tên tệp có khoảng trắng hoặc dòng mới, bạn sẽ nhận được kết quả không mong muốn.

Có hai cách lặp lại rất tốt trên các tập tin. Ở đây, tôi đã sử dụng đơn giản echođể chứng minh làm một cái gì đó với tên tệp; bạn có thể sử dụng bất cứ điều gì, mặc dù.

Đầu tiên là sử dụng các tính năng toàn cầu hóa của shell.

for dir in */; do
  echo "$dir"
done

Shell mở rộng */thành các đối số riêng biệt mà forvòng lặp đọc; ngay cả khi có một khoảng trắng, dòng mới hoặc bất kỳ ký tự lạ nào khác trong tên tệp, forsẽ thấy mỗi tên hoàn chỉnh là một đơn vị nguyên tử; nó không phân tích danh sách theo bất kỳ cách nào.

Nếu bạn muốn đi đệ quy vào thư mục con, thì điều này sẽ không làm trừ khi vỏ của bạn có một số tính năng globbing mở rộng (chẳng hạn như bash's globstar. Nếu vỏ của bạn không có tính năng này, hoặc nếu bạn muốn đảm bảo rằng kịch bản của bạn sẽ làm việc trên nhiều hệ thống khác nhau, thì tùy chọn tiếp theo là sử dụng find.

find . -type d -exec echo '{}' \;

Ở đây, findlệnh sẽ gọi echovà truyền cho nó một đối số của tên tệp. Nó làm điều này một lần cho mỗi tập tin nó tìm thấy. Như với ví dụ trước, không có phân tích danh sách tên tệp; thay vào đó, một fileneame được gửi hoàn toàn như một đối số.

Cú pháp của -execđối số có vẻ hơi buồn cười. findlấy đối số đầu tiên sau -execvà coi đó là chương trình để chạy và mọi đối số tiếp theo, nó sẽ là đối số để truyền cho chương trình đó. Có hai đối số đặc biệt -execcần xem. Thứ nhất là {}; đối số này được thay thế bằng một tên tệp mà các phần trước đó findtạo ra. Cái thứ hai là ;, cho phép findbiết đây là phần cuối của danh sách các đối số được truyền cho chương trình; findcần điều này bởi vì bạn có thể tiếp tục với nhiều đối số được dự định findvà không dành cho chương trình exec'd. Lý do \là vỏ cũng xử lý;đặc biệt - nó đại diện cho sự kết thúc của một lệnh, vì vậy chúng ta cần phải thoát nó để shell cung cấp cho nó như là một đối số findthay vì tiêu thụ nó cho chính nó; một cách khác để có được vỏ không xử lý nó đặc biệt là đặt nó trong dấu ngoặc kép: ';'hoạt động cũng như \;cho mục đích này.


2
+1 đây chắc chắn là cách để đi khi bạn cần tạo một danh sách tệp và sử dụng nó trong một lệnh. find -exec bị giới hạn bởi chỉ có thể chạy một lệnh duy nhất. Với vòng lặp, bạn có thể dẫn đến nội dung trái tim của bạn.
MaQleod

+1. Tôi muốn nhiều người sẽ sử dụng find. Có phép thuật có thể được thực hiện, và thậm chí những hạn chế nhận thức được -execcó thể được thực hiện xung quanh. Các -print0tùy chọn cũng có giá trị để sử dụng với xargs.
ghoti

Tùy chọn vòng lặp sẽ không hiển thị các thư mục ẩn. Các tùy chọn tìm kiếm wont hiển thị liên kết tượng trưng.
jinawee

16

Đối với các tệp có khoảng trắng trong bạn sẽ phải đảm bảo trích dẫn biến như:

 for i in $(ls); do echo "$i"; done; 

hoặc, bạn có thể thay đổi biến môi trường phân tách trường đầu vào (IFS):

 IFS=$'\n';for file in $(ls); do echo $i; done

Cuối cùng, tùy thuộc vào những gì bạn đang làm, bạn thậm chí có thể không cần ls:

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

sử dụng tốt một subshell trong ví dụ đầu tiên
Jeremy L

Tại sao IFS cần $ sau khi gán và trước ký tự mới?
Andy Ibanez

3
Tên tệp cũng có thể chứa dòng mới. Phá vỡ \nlà không đủ. Không bao giờ là một ý tưởng tốt để đề xuất phân tích đầu ra ls.
ghoti


4

Chỉ cần thêm vào câu trả lời của CoverosGene, đây là một cách để liệt kê chỉ các tên thư mục:

for f in */; do
  echo "Directory -> $f"
done

1

Tại sao không đặt IFS thành một dòng mới, sau đó nắm bắt đầu ra của lsmột mảng? Đặt IFS thành dòng mới sẽ giải quyết các vấn đề với các ký tự vui nhộn trong tên tệp; sử dụng lscó thể tốt bởi vì nó có một cơ sở sắp xếp tích hợp.

(Trong thử nghiệm, tôi đã gặp sự cố khi đặt IFS thành \nnhưng cài đặt nó thành công việc backspace dòng mới, như được đề xuất ở nơi khác ở đây):

Ví dụ: (giả sử lsmẫu tìm kiếm mong muốn được truyền vào $1):

SAVEIFS=$IFS
IFS=$(echo -en "\n\b")

FILES=($(/bin/ls "$1"))

for AFILE in ${FILES[@]}
do
    ... do something with a file ...
done

IFS=$SAVEIFS

Điều này đặc biệt tiện dụng trên OS X, ví dụ, để chụp danh sách các tệp được sắp xếp theo ngày tạo (cũ nhất đến mới nhất), lslệnh này là ls -t -U -r.


2
Tên tệp cũng có thể chứa dòng mới và chúng thường làm khi người dùng được phép đặt tên cho tệp của riêng mình. Phá vỡ \nlà không đủ. Các giải pháp hợp lệ duy nhất sử dụng vòng lặp for với mở rộng tên đường dẫn hoặc find.
ghoti

Cách đáng tin cậy duy nhất để chuyển danh sách tên tệp là tách chúng bằng ký tự NUL, vì đây là cách duy nhất chắc chắn không có trong đường dẫn tệp.
glglgl

-3

Đây là cách tôi làm, nhưng có lẽ có nhiều cách hiệu quả hơn.

ls > filelist.txt

while read filename; do echo filename: "$filename"; done < filelist.txt

6
Dán các ống ở vị trí của tệp:>ls | while read i; do echo filename: $i; done
Jeremy L

Mát mẻ. Tôi nên nói rằng bạn cũng có thể sử dụng $ EDITOR filelist.txt ở giữa hai lệnh. Rất nhiều thứ bạn có thể làm trong một trình soạn thảo dễ hơn trên dòng lệnh. Không liên quan đến câu hỏi này, mặc dù.
TREE

Giải pháp của bạn hoàn toàn không giải quyết được vấn đề với tên tệp chứa dòng mới và nội dung ưa thích khác.
glglgl
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.