Bagaimana cara membalik loop untuk?

26

Bagaimana cara benar melakukan forloop dalam urutan terbalik?

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

Saya membutuhkan solusi yang tidak merusak karakter funky dalam nama file.

Mehrdad
sumber
5
Hanya pipa sort -rsebelum for, atau mencuci melalui ls -r.
David Schwartz

Jawaban:

27

Dalam bash atau ksh, letakkan nama file dalam array, dan ulangi array itu dalam urutan terbalik.

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

Kode di atas juga berfungsi di zsh jika ksh_arraysopsi diset (ini dalam mode emulasi ksh). Ada metode yang lebih sederhana di zsh, yaitu membalik urutan pertandingan melalui kualifikasi glob:

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

POSIX tidak termasuk array, jadi jika Anda ingin portabel, satu-satunya pilihan Anda untuk langsung menyimpan array string adalah parameter posisi.

set -- /var/logs/foo*.log
i=$#
while [ $i -gt 0 ]; do
  eval "f=\${$i}"
  bar "$f"
  i=$((i-1))
done
Gilles 'SANGAT berhenti menjadi jahat'
sumber
Setelah Anda menggunakan parameter posisi, tidak perlu untuk variabel Anda idan f; hanya melakukan shift, gunakan $1untuk panggilan Anda bar, dan uji pada [ -z $1 ]Anda while.
user1404316
@ user1404316 Ini akan menjadi cara yang terlalu rumit untuk mengulangi elemen-elemen dalam urutan menaik . Juga, salah: tidak [ -z $1 ]atau [ -z "$1" ]tes yang berguna (bagaimana jika parameter itu *, atau string kosong?). Dan dalam kasus apa pun mereka tidak membantu di sini: pertanyaannya adalah bagaimana mengulang dalam urutan yang berlawanan.
Gilles 'SO- stop being evil'
Pertanyaannya secara spesifik mengatakan bahwa itu adalah iterasi dari nama file di / var / log, jadi aman untuk mengasumsikan bahwa tidak ada parameter yang menjadi "*" atau kosong. Saya berdiri dikoreksi, pada titik urutan terbalik.
user1404316
7

Coba ini, kecuali jika Anda menganggap jeda baris sebagai "karakter yang funky":

ls /var/logs/foo*.log | tac | while read f; do
    bar "$f"
done
sunaku
sumber
Apakah ada metode yang bekerja dengan jeda baris? (Saya tahu ini jarang terjadi, tetapi setidaknya untuk belajar saya ingin tahu apakah mungkin untuk menulis kode yang benar.)
Mehrdad
Materi iklan menggunakan tac untuk membalikkan alur, dan jika Anda ingin menghilangkan beberapa karakter yang tidak diinginkan seperti jeda baris, Anda dapat melakukan pipe ke tr -d '\ n'.
Johan
4
Ini rusak jika nama file berisi baris baru, garis miring terbalik atau karakter yang tidak patut. Lihat mywiki.wooledge.org/ParsingLs dan Bagaimana cara melompati baris file? (dan utas terkait).
Gilles 'SO- stop being evil'
Jawaban yang paling banyak memberikan suara memecahkan variabel fdalam situasi saya. Namun jawaban ini, bertindak cukup banyak sebagai pengganti drop-in untuk forgaris biasa .
Serge Stroobandt
2

Jika ada yang mencoba mencari cara untuk membalikkan iterate pada daftar string yang dibatasi ruang, ini berfungsi:

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

list="a bb ccc"

for i in `reverse $list`; do
  echo "$i"
done
> ccc
> bb 
> a
ACK_stoverflow
sumber
2
Saya tidak punya tac di sistem saya; Saya tidak tahu apakah itu kuat tetapi saya telah menggunakan untuk x dalam $ {daftar saya}; do revv = "$ {x} $ {revv}"; selesai
arp
@arp Itu agak mengejutkan karena tacmerupakan bagian dari GNU coreutils. Tapi solusi Anda juga bagus.
ACK_stoverflow
@ACK_stoverflow Ada sistem di luar sana tanpa alat pengguna Linux.
Kusalananda
@ Kusalananda Apakah Anda mengatakan bahwa hanya Linux yang menggunakan GNU coreutils? LOL. Bahkan busybox punya tac. Meskipun dari jumlah tac-penghasilan tanggapan di sini saya kira mungkin OSX tidak memiliki tac. Dalam hal ini gunakan beberapa varian solusi @ arp - bagaimanapun juga lebih mudah untuk dipahami.
ACK_stoverflow
@ACK_stoverflow Itulah yang saya katakan, ya.
Kusalananda
1
find /var/logs/ -name 'foo*.log' -print0 | tail -r | xargs -0 bar

Harus beroperasi seperti yang Anda inginkan (ini diuji pada Mac OS X dan saya memiliki peringatan di bawah ...).

Dari halaman manual untuk menemukan:

-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).

Pada dasarnya, Anda menemukan file yang cocok dengan string + glob Anda dan mengakhiri masing-masing dengan karakter NUL. Jika nama file Anda mengandung baris baru atau karakter aneh lainnya, find harus menangani ini dengan baik.

tail -r

mengambil input standar melalui pipa dan membalikkannya (perhatikan bahwa tail -rmencetak semua input ke stdout, dan bukan hanya 10 baris terakhir, yang merupakan standar standar. man tailuntuk info lebih lanjut).

Kami kemudian mengirimkannya ke 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).

Di sini, xargs mengharapkan untuk melihat argumen yang dipisahkan oleh karakter NUL, yang Anda berikan finddan balikkan tail.

Peringatan saya: Saya pernah baca yang tailtidak cocok dengan string yang diakhiri null . Ini bekerja dengan baik pada Mac OS X, tetapi saya tidak dapat menjamin bahwa ini berlaku untuk semua * nixes. Tapak dengan hati-hati.

Saya juga harus menyebutkan bahwa GNU Parallel sering digunakan sebagai xargsalternatif. Anda dapat memeriksanya juga.

Saya mungkin kehilangan sesuatu, jadi orang lain harus berpadu.

tcdyl
sumber
2
+1 jawaban yang bagus. Sepertinya Ubuntu tidak mendukung tail -r... apakah saya melakukan sesuatu yang salah?
Mehrdad
1
Tidak, kurasa tidak. Saya tidak memiliki mesin linux saya tetapi google cepat untuk 'linux tail man' tidak menunjukkannya sebagai opsi. sunaku disebut tacsebagai alternatif, jadi saya akan mencobanya, sebagai gantinya
tcdyl
Saya juga mengedit jawaban untuk menyertakan alternatif lain
tcdyl
whoops Saya lupa memberi +1 ketika saya berkata +1 :( Selesai! Maaf soal itu haha ​​:)
Mehrdad
4
tail -rkhusus untuk OSX, dan membalikkan input yang dibatasi-baris, bukan input yang dibatasi-nol. Solusi kedua Anda tidak bekerja sama sekali (Anda memasukkan masukan ke ls, yang tidak peduli); tidak ada perbaikan mudah yang akan membuatnya bekerja dengan andal.
Gilles 'SO- stop being evil'
1

Dalam contoh Anda, Anda mengulang-ulang beberapa file, tetapi saya menemukan pertanyaan ini karena judulnya yang lebih umum yang juga dapat mencakup pengulangan pada array, atau membalikkan berdasarkan jumlah pesanan.

Berikut cara melakukannya di Zsh:

Jika Anda mengulang elemen di dalam array, gunakan sintaks ini ( sumber )

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

Omembalikkan urutan yang ditentukan dalam bendera berikutnya; aadalah susunan array normal.

Seperti yang dikatakan @Gilles, Onakan membalik urutan file globbed Anda, misalnya dengan my/file/glob/*(On). Itu karena On" urutan nama terbalik ."

Zsh sort flags:

  • a urutan array
  • L panjang file
  • l jumlah tautan
  • m tanggal modifikasi
  • n nama
  • ^ourutan terbalik ( oadalah urutan normal)
  • O urutan terbalik

Sebagai contoh, lihat https://github.com/grml/zsh-lovers/blob/master/zsh-lovers.1.txt dan http://reasoniamhere.com/2014/01/11/outrageously-useful-tips- to-master-your-z-shell /

Henry
sumber
0

Mac OSX tidak mendukung tacperintah. Solusi oleh @tcdyl bekerja ketika Anda memanggil satu perintah dalam satu forlingkaran. Untuk semua kasus lain, berikut ini adalah cara paling sederhana untuk menyiasatinya.

Pendekatan ini tidak mendukung memiliki baris baru di nama file Anda. Alasan yang mendasari adalah, yang tail -rmemilah inputnya seperti dibatasi oleh baris baru.

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

Namun, ada cara untuk mengatasi batasan baris baru. Jika Anda tahu nama file Anda tidak mengandung karakter tertentu (katakanlah, '='), maka Anda dapat menggunakan truntuk mengganti semua baris baru untuk menjadi karakter ini, dan kemudian melakukan pengurutan. Hasilnya akan terlihat sebagai berikut:

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

Catatan: tergantung pada versi Anda tr, itu mungkin tidak mendukung '\0'sebagai karakter. Ini biasanya dapat dikerjakan dengan mengubah lokal ke C (tapi saya tidak ingat bagaimana tepatnya, karena setelah memperbaikinya sekarang berfungsi di komputer saya). Jika Anda mendapatkan pesan kesalahan, dan Anda tidak dapat menemukan solusinya, silakan kirimkan sebagai komentar, jadi saya dapat membantu Anda memecahkan masalah itu.

Alex
sumber
0

cara mudah adalah menggunakan ls -rseperti yang disebutkan oleh @ David Schwartz

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

Coba ini:

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

Saya pikir ini adalah cara yang paling sederhana.

Syhra
sumber
3
Pertanyaannya adalah “ forloop dalam urutan terbalik”.
manatwork