Làm thế nào để tôi đảo ngược một vòng lặp for?


26

Làm thế nào để tôi thực hiện đúng một forvòng lặp theo thứ tự ngược lại?

for f in /var/logs/foo*.log; do
    bar "$f"
done

Tôi cần một giải pháp không phá vỡ các ký tự sôi nổi trong tên tệp.


5
Chỉ cần đường ống đến sort -rtrước for, hoặc giặt qua ls -r.
David Schwartz

Câu trả lời:


27

Trong bash hoặc ksh, đặt tên tệp trong một mảng và lặp lại trên mảng đó theo thứ tự ngược lại.

files=(/var/logs/foo*.log)
for ((i=${#files[@]}-1; i>=0; i--)); do
  bar "${files[$i]}"
done

Mã ở trên cũng hoạt động trong zsh nếu ksh_arraystùy chọn được đặt (nó ở chế độ mô phỏng ksh). Có một phương pháp đơn giản hơn trong zsh, đó là đảo ngược thứ tự của các trận đấu thông qua vòng loại toàn cầu:

for f in /var/logs/foo*.log(On); do bar $f; done

POSIX không bao gồm các mảng, vì vậy nếu bạn muốn di động, tùy chọn duy nhất của bạn để lưu trữ trực tiếp một chuỗi các chuỗi là các tham số vị trí.

set -- /var/logs/foo*.log
i=$#
while [ $i -gt 0 ]; do
  eval "f=\${$i}"
  bar "$f"
  i=$((i-1))
done

Khi bạn đang sử dụng các tham số vị trí, không cần các biến của bạn if; chỉ cần thực hiện một shift, sử dụng $1cho cuộc gọi của bạn barvà kiểm tra [ -z $1 ]trong của bạn while.
dùng1404316

@ user1404316 Đây sẽ là một cách lặp quá phức tạp đối với các phần tử theo thứ tự tăng dần . Ngoài ra, sai: không phải [ -z $1 ]cũng không phải [ -z "$1" ]là các thử nghiệm hữu ích (nếu tham số là *hoặc chuỗi rỗng thì sao?). Và trong mọi trường hợp họ không giúp đỡ ở đây: câu hỏi là làm thế nào để lặp theo thứ tự ngược lại.
Gilles 'SO- ngừng trở nên xấu xa'

Câu hỏi đặc biệt nói rằng nó đang lặp qua các tên tệp trong / var / log, vì vậy sẽ an toàn khi cho rằng không có tham số nào là "*" hoặc trống. Tôi đã đứng đúng, mặc dù, trên quan điểm của thứ tự ngược lại.
user1404316

7

Hãy thử điều này, trừ khi bạn coi ngắt dòng là "ký tự sôi nổi":

ls /var/logs/foo*.log | tac | while read f; do
    bar "$f"
done

Có một phương pháp làm việc với ngắt dòng? (Tôi biết đó là hiếm, nhưng ít nhất vì lợi ích của việc học tôi muốn biết nếu nó có thể viết mã chính xác.)
Mehrdad

Sáng tạo để sử dụng tac để đảo ngược dòng chảy và nếu bạn muốn loại bỏ một số ký tự không mong muốn như ngắt dòng, bạn có thể chuyển sang tr -d '\ n'.
Johan

4
Điều này phá vỡ nếu tên tệp chứa dòng mới, dấu gạch chéo ngược hoặc ký tự không thể in. Xem mywiki.wooledge.org/ParsingLsCách lặp qua các dòng của một tệp? (và các chủ đề được liên kết).
Gilles 'SO- ngừng trở nên xấu xa'

Câu trả lời được bình chọn nhiều nhất đã phá vỡ biến ftrong tình huống của tôi. Câu trả lời này, hoạt động khá nhiều như là một thay thế thả xuống cho fordòng thông thường .
Serge Stroobandt 6/07/2015

2

Nếu bất cứ ai đang cố gắng tìm ra cách đảo ngược lặp lại trong danh sách chuỗi được phân tách bằng dấu cách, thì điều này hoạt động:

reverse() {
  tac <(echo "$@" | tr ' ' '\n') | tr '\n' ' '
}

list="a bb ccc"

for i in `reverse $list`; do
  echo "$i"
done
> ccc
> bb 
> a

2
Tôi không có tac trên hệ thống của tôi; Tôi không biết nếu nó mạnh mẽ nhưng tôi đã sử dụng cho x trong $ {mylist}; làm revv = "$ {x} $ {revv}"; xong
arp

@arp Đó là một loại gây sốc như taclà một phần của lõi GNU. Nhưng giải pháp của bạn cũng là một trong những tốt.
ACK_stoverflow

@ACK_stoverflow Có những hệ thống không có công cụ người dùng Linux.
Kusalananda

@Kusalananda Bạn đang nói rằng chỉ Linux sử dụng lõi GNU? LOL. Ngay cả busybox cũng có tac. Mặc dù từ số lượng tacphản hồi không có ở đây, tôi đoán rằng có lẽ OSX không có tac. Trong trường hợp đó, hãy sử dụng một số biến thể của giải pháp @ arp - dù sao cũng dễ hiểu hơn.
ACK_stoverflow 16/2/18

@ACK_stoverflow Đó là những gì tôi đang nói, vâng.
Kusalananda

1
find /var/logs/ -name 'foo*.log' -print0 | tail -r | xargs -0 bar

Nên vận hành theo cách bạn muốn (điều này đã được thử nghiệm trên Mac OS X và tôi có một cảnh báo bên dưới ...).

Từ trang người đàn ông để tìm:

-print0
         This primary always evaluates to true.  It prints the pathname of the current file to standard output, followed by an ASCII NUL character (charac-
         ter code 0).

Về cơ bản, bạn đang tìm các tệp khớp với chuỗi + toàn cầu của mình và kết thúc mỗi tệp có ký tự NUL. Nếu tên tệp của bạn chứa dòng mới hoặc các ký tự lạ khác, hãy tìm cách xử lý tốt việc này.

tail -r

đưa đầu vào tiêu chuẩn qua đường ống và đảo ngược nó (lưu ý rằng tail -rin tất cả đầu vào thành thiết bị xuất chuẩn, và không chỉ 10 dòng cuối cùng, là mặc định tiêu chuẩn. man tailđể biết thêm).

Chúng tôi sau đó dẫn đường đó đến xargs -0:

-0      Change xargs to expect NUL (``\0'') characters as separators, instead of spaces and newlines.  This is expected to be used in concert with the
         -print0 function in find(1).

Ở đây, xargs hy vọng sẽ thấy các đối số được phân tách bằng ký tự NUL mà bạn truyền từ findvà đảo ngược với tail.

Lời cảnh báo của tôi: Tôi đã đọc rằng tailnó không chơi tốt với các chuỗi kết thúc null . Điều này hoạt động tốt trên Mac OS X, nhưng tôi không thể đảm bảo đó là trường hợp cho tất cả * nixes. Bước đi cẩn thận.

Tôi cũng nên đề cập rằng GNU Parallel thường được sử dụng xargsthay thế. Bạn cũng có thể kiểm tra xem.

Tôi có thể đang thiếu một cái gì đó, vì vậy những người khác nên kêu vang.


2
+1 câu trả lời tuyệt vời. Có vẻ như Ubuntu không hỗ trợ tail -rmặc dù ... tôi có làm gì sai không?
Mehrdad

1
Không, tôi không nghĩ bạn là. Tôi không có máy linux của mình nhưng google nhanh cho 'linux tail man' không hiển thị dưới dạng tùy chọn. sunaku được đề cập tacnhư một giải pháp thay thế, vì vậy tôi sẽ thử điều đó, thay vào đó
tcdyl

Tôi cũng đã chỉnh sửa câu trả lời để đưa vào một phương án khác
tcdyl

Rất tiếc tôi đã quên +1 khi tôi nói +1 :( Xong! Xin lỗi về điều đó haha ​​:)
Mehrdad

4
tail -rdành riêng cho OSX và đảo ngược đầu vào được phân tách bằng dòng mới, không phải đầu vào được phân tách bằng null. Giải pháp thứ hai của bạn hoàn toàn không hoạt động (bạn đang đầu vào ls, không quan tâm); không có sửa chữa dễ dàng mà sẽ làm cho nó hoạt động đáng tin cậy.
Gilles 'SO- ngừng trở nên xấu xa'

1

Trong ví dụ của bạn, bạn đang lặp qua một số tệp, nhưng tôi đã tìm thấy câu hỏi này vì tiêu đề chung hơn của nó cũng có thể bao gồm việc lặp qua một mảng hoặc đảo ngược dựa trên bất kỳ số lượng đơn đặt hàng nào.

Đây là cách để làm điều đó trong Zsh:

Nếu bạn đang lặp qua các phần tử trong một mảng, hãy sử dụng cú pháp ( nguồn ) này

for f in ${(Oa)your_array}; do
    ...
done

Ođảo ngược thứ tự được chỉ định trong cờ tiếp theo; alà thứ tự mảng bình thường.

Như @Gilles đã nói, Onsẽ đảo ngược thứ tự các tập tin toàn cầu của bạn, ví dụ như với my/file/glob/*(On). Đó là bởi vì On" thứ tự tên ngược ."

Cờ sắp xếp Zsh:

  • a thứ tự mảng
  • L chiều dài tập tin
  • l số lượng liên kết
  • m Ngày sửa đổi
  • n Tên
  • ^othứ tự ngược lại ( olà thứ tự bình thường)
  • O trật tự ngược

Ví dụ: xem https://github.com/grml/zsh-lovers/blob/master/zsh-lovers.1.txthttp://reasoniamhere.com/2014/01/11/outrageiously-usiously-tips- to-master-your-z-shell /


0

Mac OSX không hỗ trợ taclệnh. Giải pháp của @tcdyl hoạt động khi bạn đang gọi một lệnh đơn trong một forvòng lặp. Đối với tất cả các trường hợp khác, sau đây là cách đơn giản nhất để khắc phục nó.

Cách tiếp cận này không hỗ trợ việc có dòng mới trong tên tệp của bạn. Lý do cơ bản là, tail -rsắp xếp đầu vào của nó như được phân định bởi các dòng mới.

for i in `ls -1 [filename pattern] | tail -r`; do [commands here]; done

Tuy nhiên, có một cách để vượt qua giới hạn dòng mới. Nếu bạn biết tên tệp của mình không chứa một ký tự nào đó (giả sử '='), thì bạn có thể sử dụng trđể thay thế tất cả các dòng mới để trở thành ký tự này, sau đó thực hiện sắp xếp. Kết quả sẽ như sau:

for i in `find [directory] -name '[filename]' -print0 | tr '\n' '=' | tr '\0' '\n'
| tail -r | tr '\n' '\0' | tr '=' '\n' | xargs -0`; do [commands]; done

Lưu ý: tùy thuộc vào phiên bản của bạn tr, nó có thể không hỗ trợ '\0'dưới dạng ký tự. Điều này thường có thể được giải quyết bằng cách thay đổi ngôn ngữ thành C (nhưng tôi không nhớ chính xác như thế nào, vì sau khi sửa nó một lần thì nó hoạt động trên máy tính của tôi). Nếu bạn nhận được thông báo lỗi và bạn không thể tìm thấy cách giải quyết, thì vui lòng gửi nó dưới dạng nhận xét, để tôi có thể giúp bạn khắc phục sự cố.


0

một cách dễ dàng là sử dụng ls -rnhư được đề cập bởi @David Schwartz

for f in $(ls -r /var/logs/foo*.log); do
    bar "$f"
done

-4

Thử đi:

for f in /var/logs/foo*.log; do
bar "$f"
done

Tôi nghĩ đó là cách đơn giản nhất.


3
Câu hỏi đặt ra cho forvòng lặp của người Viking theo thứ tự ngược lại.
manatwork
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.