петля bash з кроком 0,02


11

Я хочу зробити цикл for в bash з збільшенням 0,02, який я намагався

for ((i=4.00;i<5.42;i+=0.02))
do
commands
done

але це не спрацювало.


9
Bash не займається математикою з плаваючою комою.
Йорданм

1
приріст може бути зроблений bc, але зупинка на 4,52 може бути складним. використовувати пропозицію @roaima, мати допоміжний var із кроком 2 та використовуватиi=$(echo $tmp_var / 100 | bc)
Archemar


5
Зазвичай ви не хочете використовувати поплавці як індекс циклу . Ви накопичуєте помилку на кожній ітерації.
isanae

Відповіді:


18

Читання bash довідкової сторінки дає таку інформацію:

for (( expr1 ; expr2 ; expr3 )) ; do list ; done

По-перше, арифметичний вираз expr1оцінюється за правилами, описаними нижче в АРИТМЕТИЧНІЙ ОЦІНКІ. [...]

і тоді ми отримуємо цей розділ

АРИТМЕТИЧНА ОЦІНКА

Оболонка дозволяє оцінювати арифметичні вирази за певних обставин (див. letІ declareвбудовані команди та Арифметичне розширення). Оцінка проводиться в цілих цілих числах без перевірки на переповнення [...]

Тож добре видно, що ви не можете використовувати forцикл із не цілими значеннями.

Одним з варіантів може бути просто помножити всі ваші компоненти циклу на 100, дозволяючи це робити там, де ви згодом їх використовуєте, наприклад:

for ((k=400;k<542;k+=2))
do
    i=$(bc <<<"scale=2; $k / 100" )    # when k=402 you get i=4.02, etc.
    ...
done

Я думаю, що це найкраще рішення, оскільки k=400;k<542;k+=2воно дозволяє уникнути потенційних арифметичних проблем з плаваючою комою.
Гюйгенс

1
Зауважте, що для кожної ітерації в циклі ви створюєте трубу (для зчитування результатів bc), розщеплюєте процес, створюєте тимчасовий файл (для ряду here), виконуєте bcв ньому (що передбачає завантаження виконуваної та спільної бібліотеки та ініціалізуючи їх), дочекайтеся його та очищення. Запустити bcодин раз, щоб зробити цикл, було б набагато ефективніше.
Стефан Шазелас

@ StéphaneChazelas так, погодився. Але якщо це вузьке місце, то, мабуть, ми все одно пишемо код невірною мовою. OOI, який менш неефективний (!)? i=$(bc <<< "scale...")абоi=$(echo "scale..." | bc)
roaima

1
З мого швидкого тесту, трубна версія швидша в zsh (звідки <<<походить), bashі ksh. Зауважте, що перехід на іншу оболонку bashдозволить покращити продуктивність, ніж у будь-якому випадку використовувати інший синтаксис.
Стефан Шазелас

(І більшість з оболонок , що підтримка <<<(ЗШ, МКШ, ksh93, Йаш) також підтримує арифметику з плаваючою комою ( zsh, ksh93, yash)).
Stéphane Chazelas

18

Уникайте петель в оболонках.

Якщо ви хочете займатися арифметикою, використовуйте awkабо bc:

awk '
  BEGIN{
    for (i = 4.00; i < 5.42; i+ = 0.02)
      print i
  }'

Або

bc << EOF
for (i = 4.00; i < 5.42; i += 0.02)  i
EOF

Зауважте, що awk(всупереч bc) працює з вашими процесорами, doubleпредставлення числа з плаваючою комою (ймовірно, тип IEEE 754 ). Як результат, оскільки ці числа є двійковими наближеннями цих десяткових чисел, у вас можуть виникнути деякі сюрпризи:

$ gawk 'BEGIN{for (i=0; i<=0.3; i+=0.1) print i}'
0
0.1
0.2

Якщо ви додасте, OFMT="%.17g"ви можете побачити причину відсутності 0.3:

$ gawk 'BEGIN{OFMT="%.17g"; for (i=0; i<=0.5; i+=0.1) print i}'
0
0.10000000000000001
0.20000000000000001
0.30000000000000004
0.40000000000000002
0.5

bc робить довільну точність, тому не має подібних проблем.

Зауважте, що за замовчуванням (якщо ви не змінюєте формат виводу за допомогою OFMTабо не використовуєте printfз явними специфікаціями формату), awkвикористовується %.6gдля відображення чисел з плаваючою комою, тому перемикається на 1e6 і вище для чисел з плаваючою комою вище 1 000 000 і обрізає дробову частину для високих чисел (100000.02 відображатиметься як 100000).

Якщо вам дійсно потрібно використовувати цикл оболонки, тому що, наприклад, ви хочете виконати конкретні команди для кожної ітерації цього циклу, або використовуйте оболонку з арифметичною підтримкою з плаваючою комою zsh, yashабо, ksh93або генеруйте список значень однією командою, як вище (або seqза наявності) та переведіть на її вихід.

Люблю:

unset -v IFS # configure split+glob for default word splitting
for i in $(seq 4 0.02 5.42); do
  something with "$i"
done

Або:

seq 4 0.02 5.42 | while IFS= read i; do
  something with "$i"
done

якщо ви не натискаєте обмеження чисел плаваючої точки на вашому процесорі, seqвиправляєте помилки, нанесені наближеннями плаваючої точки, більш витончено, ніж це awkбуло б у версії, наведеній вище.

Якщо у вас немає seq(команда GNU), ви можете зробити більш надійний як функцію, наприклад:

seq() { # args: first increment last
  bc << EOF
    for (i = $1; i <= $3; i += $2) i
EOF
}

Це було б краще для таких речей seq 100000000001 0.000000001 100000000001.000000005. Однак зауважте, що наявність чисел з довільно високою точністю не допоможе багато, якщо ми збираємось передавати їх командам, які їх не підтримують.


Я ціную використання awk! +1
Пандія

Навіщо це потрібно unset IFSв першому прикладі?
користувач1717828

@ user1717828, в ідеалі з цим викликом розділення + глобус ми хочемо розділити на символи нового рядка. Ми можемо це зробити, IFS=$'\n'але це не працює у всіх оболонках. Або IFS='<a-litteral-newline-here>'але це не дуже розбірливо. Або ми можемо розділити на слова замість цього (пробіл, вкладка, новий рядок), як ви отримаєте зі значенням за замовчуванням $ IFS або якщо ви вимкнете IFS і також працює тут.
Стефан Шазелас

@ user1717828: нам не потрібно возитися IFS, тому що ми знаємо, що seqу вихідних даних немає пробілів, які нам не потрібні. Це здебільшого, щоб переконатися, що ви розумієте, що цей приклад залежить від того IFS, що може мати значення для іншої команди, що генерує список.
Пітер Кордес

1
@PeterCordes, це там, тому нам не потрібно робити жодних припущень щодо того, що IFS було встановлено заздалегідь.
Стефан Шазелас


0

Як запропонували інші, ви можете використовувати bc:

i="4.00"

while [[ "$(bc <<< "$i < 5.42")" == "1" ]]; do
    # do something with i
    i="$(bc <<< "$i + 0.02")"
done
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.