Meniru loop do-while di Bash


142

Apa cara terbaik untuk meniru loop do-while di Bash?

Saya dapat memeriksa kondisi sebelum memasuki whileloop, dan kemudian melanjutkan memeriksa kembali kondisi dalam loop, tetapi itu adalah kode duplikat. Apakah ada cara yang lebih bersih?

Kode semu dari skrip saya:

while [ current_time <= $cutoff ]; do
    check_if_file_present
    #do other stuff
done

Ini tidak berfungsi check_if_file_presentjika diluncurkan setelah $cutoffwaktu tersebut, dan do-while akan melakukannya.


Apakah Anda mencari untilsakelar?
Michael Gardner

2
@MichaelGardner untiljuga akan mengevaluasi kondisi sebelum menjalankan badan loop
Alex

2
Ah, begitu, saya salah paham dengan kebingungan Anda.
Michael Gardner

Jawaban:


228

Dua solusi sederhana:

  1. Jalankan kode Anda sekali sebelum while loop

    actions() {
       check_if_file_present
       # Do other stuff
    }
    
    actions #1st execution
    while [ current_time <= $cutoff ]; do
       actions # Loop execution
    done
    
  2. Atau:

    while : ; do
        actions
        [[ current_time <= $cutoff ]] || break
    done
    

10
:adalah bawaan, setara dengan bawaan true. Mereka berdua "tidak melakukan apa pun dengan sukses".
loxaxs

2
@loxaxs itu benar di misalnya zsh tetapi tidak di Bash. trueadalah program aktual sedangkan :bawaan. Yang pertama hanya keluar dengan 0(dan falsedengan 1) yang terakhir sama sekali tidak melakukan apa-apa. Anda bisa mengeceknya dengan which true.
Fleshgrinder

@Fleshgrinder :masih dapat digunakan sebagai pengganti truedi Bash. Cobalah dengan while :; do echo 1; done.
Alexej Magura

2
Tidak pernah mengatakan sesuatu yang berbeda, hanya saja itu truebukan Bash bawaan. Ini adalah program yang biasanya ditemukan di /bin.
Fleshinder

1
type truedi bash (kembali ke bash 3.2) kembali true is a shell builtin. Memang benar itu /bin/trueadalah sebuah program; apa yang tidak benar tentang yang benar adalah itu truebukan bawaan. (tl; dr: true adalah bash builtin DAN program)
PJ Eby

153

Tempatkan badan loop Anda setelah whiledan sebelum tes. Badan whileloop yang sebenarnya harus berupa no-op.

while 
    check_if_file_present
    #do other stuff
    (( current_time <= cutoff ))
do
    :
done

Alih-alih titik dua, Anda dapat menggunakan continuejika menurut Anda lebih mudah dibaca. Anda juga dapat memasukkan perintah yang hanya akan berjalan di antara iterasi (bukan sebelum pertama atau setelah terakhir), seperti echo "Retrying in five seconds"; sleep 5. Atau cetak pembatas antar nilai:

i=1; while printf '%d' "$((i++))"; (( i <= 4)); do printf ','; done; printf '\n'

Saya mengubah tes untuk menggunakan tanda kurung ganda karena Anda tampaknya membandingkan bilangan bulat. Di dalam tanda kurung siku ganda, operator pembanding seperti <=bersifat leksikal dan akan memberikan hasil yang salah saat membandingkan 2 dan 10, misalnya. Operator tersebut tidak bekerja di dalam tanda kurung siku tunggal.


Apakah itu setara dengan satu baris while { check_if_file_present; ((current_time<=cutoff)); }; do :; done? Yaitu apakah perintah di dalam whilekondisi secara efektif dipisahkan oleh titik koma bukan oleh misalnya &&, dan dikelompokkan oleh {}?
Ruslan

@Ruslan: Tanda kurung kurawal tidak diperlukan. Anda tidak boleh menautkan apa pun ke pengujian di dalam tanda kurung ganda menggunakan &&atau ||karena hal itu secara efektif menjadikannya bagian dari pengujian yang mengontrol while. Kecuali Anda menggunakan konstruksi ini pada baris perintah, saya tidak akan melakukannya sebagai satu baris (dalam skrip, khususnya) karena maksudnya tidak dapat dibaca.
Dijeda sampai pemberitahuan lebih lanjut.

Ya, saya tidak bermaksud menggunakannya sebagai satu baris: hanya untuk menjelaskan bagaimana perintah dalam pengujian terhubung. Saya khawatir bahwa perintah pertama yang mengembalikan bukan nol mungkin membuat seluruh kondisi menjadi salah.
Ruslan

3
@ruslan: Tidak, ini adalah nilai pengembalian terakhir. while false; false; false; true; do echo here; break; doneoutputs "here"
Dijeda hingga pemberitahuan lebih lanjut.

@thatotherguy: Antara kemampuan cukup keren! Anda juga bisa menggunakannya untuk menyisipkan pemisah dalam string. Terima kasih!
Dijeda sampai pemberitahuan lebih lanjut.

3

Kita dapat meniru loop do-while di Bash dengan while [[condition]]; do true; doneseperti ini:

while [[ current_time <= $cutoff ]]
    check_if_file_present
    #do other stuff
do true; done

Sebagai contoh. Berikut implementasi saya untuk mendapatkan koneksi ssh dalam skrip bash:

#!/bin/bash
while [[ $STATUS != 0 ]]
    ssh-add -l &>/dev/null; STATUS="$?"
    if [[ $STATUS == 127 ]]; then echo "ssh not instaled" && exit 0;
    elif [[ $STATUS == 2 ]]; then echo "running ssh-agent.." && eval `ssh-agent` > /dev/null;
    elif [[ $STATUS == 1 ]]; then echo "get session identity.." && expect $HOME/agent &> /dev/null;
    else ssh-add -l && git submodule update --init --recursive --remote --merge && return 0; fi
do true; done

Ini akan memberikan keluaran secara berurutan seperti di bawah ini:

Step #0 - "gcloud": intalling expect..
Step #0 - "gcloud": running ssh-agent..
Step #0 - "gcloud": get session identity..
Step #0 - "gcloud": 4096 SHA256:XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX /builder/home/.ssh/id_rsa (RSA)
Step #0 - "gcloud": Submodule '.google/cloud/compute/home/chetabahana/.docker/compose' (git@github.com:chetabahana/compose) registered for path '.google/cloud/compute/home/chetabahana/.docker/compose'
Step #0 - "gcloud": Cloning into '/workspace/.io/.google/cloud/compute/home/chetabahana/.docker/compose'...
Step #0 - "gcloud": Warning: Permanently added the RSA host key for IP address 'XXX.XX.XXX.XXX' to the list of known hosts.
Step #0 - "gcloud": Submodule path '.google/cloud/compute/home/chetabahana/.docker/compose': checked out '24a28a7a306a671bbc430aa27b83c09cc5f1c62d'
Finished Step #0 - "gcloud"

2

Implementasi ini:

  • Tidak memiliki duplikasi kode
  • Tidak membutuhkan fungsi tambahan ()
  • Tidak bergantung pada nilai kembalian kode di bagian "while" dari perulangan:
do=true
while $do || conditions; do
  do=false
  # your code ...
done

Ia bekerja dengan loop baca, juga, melewatkan pembacaan pertama:

do=true
while $do || read foo; do
  do=false

  # your code ...
  echo $foo
done
Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.