Как я могу сделать сценарий, чтобы считать на пять?


10

Я пытался сделать очень простой bash-скрипт для перечисления всех пятикратных чисел между 375 и 3500 (375, 380, 385 ...). Одна вещь, которую я пробовал и не работал:

for i in {375..3500}
do
        echo $i
        (($i += 5))
done

Через некоторое время я сдался и написал это на бейсике примерно через 15 секунд:

10 count = 375
20 print count
30 count = count+5
40 if count < 3500 then goto 20

Как я могу сделать свою БАЗОВУЮ программу в bash-скрипте?


это лучше при переполнении стека - Unix & Linux - это ОС, а не кодирование. Кодирование для стекопотока.
noɥʇʎԀʎzɐɹƆ

Ответы:


20

В качестве альтернативы можно использовать традиционный C-стиль для цикла:

for ((i=375; i<=3500; i+=5)); do
    echo $i
done

Это, возможно, менее понятно, чем использование seq, но не порождает никаких подпроцессов. Хотя, поскольку я знаком с C, у меня не возникнет никаких затруднений с пониманием этого, кроме YMMV.


1
Недостатком этого метода является то, что он менее переносим. Метод seq работает в POSIX sh.
Воутер Верхелст

О, нет ли C-стиля для циклов в POSIX sh? Вы узнаете что-то новое каждый день ...
Muzer

2
@WouterVerhelst Ну, кроме случаев, когда вы ограничены POSIX sh, у вас вряд ли будет seq, который является частью GNU coreutils. У busybox есть более ограниченная реализация, но вне этого, многие встроенные системы, скорее всего, не будут иметь ее вообще.
Крис Даун

1
@Muzer - по крайней мере dash, не поддерживает его. @ChrisDown - нет никаких причин, почему «POSIX sh» должен подразумевать «no seq». Но да, конечно, я упустил из виду тот факт, что seq не указан в POSIX.
Ваутер Верхелст

27

Так как вы все равно используете расширение скобок, полностью используйте его функцию:

echo {375..3500..5}

Вы также можете использовать эту технику для печати каждого числа в отдельной строке с необязательным текстом, используя printfвместо echo, например:

$ printf "Number %s is generated.\n" {375..3500..5}
Number 375 is generated.
Number 380 is generated.
Number 385 is generated.
...

редактировать

Как отметил @kojiro в комментарии, Mac OS использует bash 3 в качестве оболочки по умолчанию, которая не поддерживает приращение в выражении последовательности расширения скобки. Вам необходимо обновить Bash до версии 4 или использовать другую оболочку, которая поддерживает это (например, недавняя zsh).


2
Это не работает для меня в Mac OS 10.10.3. Он выводит «{375..3500..5}».
aswine

6
@aswine, поэтому вы всегда должны указывать свою ОС при запросе. Тем более что OSX имеет ряд отличий как от других UNICE, так и от Linux.
Тердон

2
Это не разница ОС как таковая. Это разница в версии. OS X имеет только BASH 3 OOTB. Вы можете установить BASH этого века, используя практически любой менеджер пакетов OS X. Я использую Home Brew, например.
Кодзиро

2
Тот факт, что расширения терпят неудачу, запускается напрямую, но успешно в bash -cзначительной степени гарантирует участие двух разных оболочек. Говорит ли $SHELL --versionэто, что это Bash 3?
дхаг

3
@mikeserv Ответ очень прост - я не писал этого, потому что понятия не имел, в какой версии bashбыла представлена ​​эта функция. Я узнал это сам из комментариев Кодзиро. И, пожалуйста, не сравнивайте меня с СЦ, я действительно не знаю всех деталей и истории всех оболочек с конца 1970 года. Еще раз - если вы этого ожидаете, просто, пожалуйста, понизьте.
Джимми

17

Использование SEQ (1)

for i in $(seq 375 5 3500)
do
    echo $i
done

Или просто:

seq 375 5 3500

4
Еще проще было бы отбросить цикл и просто использовать seq 375 5 3500.
дхаг

11

Ваш фрагмент цикла for не работает, как вам требуется по двум причинам:

  • (($i += 5))- здесь $iрасширяется до значения i. Таким образом, расширение будет чем-то вроде этого ((375 += 5)), что не имеет смысла (попытка присвоить литеральное число другому литеральному номеру). Обычно это достигается с помощью ((i += 5))(нет, $чтобы расширить переменную)
  • {375..3500}Будет расширен до первой итерации цикла. Это будет список номеров 375 376 ... 3499 3500. Для каждой итерации цикла, iбудут присваиваться каждому из этих номеров, один за другим. Таким образом, в начале каждой итерации iбудет переназначаться следующее значение в этом списке, считая с шагом 1. ((i += 5))Фактически ничего не делает - он добавляет 5 к i, но затем я просто переназначается снова в начале Следующая итерация.

Я думаю, что мне больше нравится for (( ; ; ))ответ, но вот несколько вариантов, чтобы вы думали:


Поскольку мы имеем дело с коэффициентами, кратными 5, и {a..b..i}расширение не поддерживается в bash версии 3.2.57 (1) (в OS X), то вместо этого мы можем сделать это немного загадочно:

for i in 375 {38..349}{0,5} 3500; do
    echo $i
done

Это демонстрирует, как bash может быть использован для создания декартового произведения.


В общем, я думаю, что цикл for является наиболее удобным способом сделать это, но если вам интересно, вы можете использовать цикл while (немного ближе к вашему БЕЙСИКУ):

count=375
while (( count <= 3500 )); do
    echo $count
    (( count += 5 ))
done

1
@jimmij ... что такое декартово произведение? Вы можете уменьшить комментарий, если хотите, и мне будет все равно, если вы скажете мне. Мне любопытно.
mikeserv

1
@mikeserv Вы не можете понизить комментарий: en.wikipedia.org/wiki/Cartesian_product
Джимми

@jimmij - со мной все в порядке, если ты это сделаешь.
mikeserv

@jimmij Дьявол, ты о чем? Декартово произведение - это набор кортежей каждой возможной комбинации элементов из двух наборов (которые могут быть или не быть одним и тем же набором). Говоря языком, это просто «все возможные комбинации» из двух групп вещей. Я вижу только один сет. Как здесь декартово произведение?
jpmc26

1
@ jpmc26 Вы правы, это «все возможные комбинации», поэтому давайте построим две оси - сначала запишем все числа от 38до 349. На втором только два номера: 0а 5. Теперь давайте создавать упорядоченные пары этих всех комбинаций - вы можете написать их как наборы: {38,0}, {38,5}... {349,5}, или просто удалить лишние символы {, ,а }и получить новые номера ... 380... 3495.
Джимми

5

Хотя, конечно, есть приложение для этого ( seq 375 5 3500), есть разные способы сделать это из командной строки. Хотя самый быстрый и простой будет только использовать seq, вот некоторые другие варианты:

for i in {375..3500}; do [[ (($i % 5)) -eq 0 ]] && echo $i; done

i=370; while [ $i -le 3500 ]; do printf "%s\n" $((i+=5)); done

perl -le '$i=shift;while($i<=$ARGV[0]){print $i; $i+=5; }' 375 3500
perl -le 'map{print $_ + 5} 370..3495'

awk 'BEGIN{ for(i=375;i<=3500;i+=5){print i}}'

Nit pick: $(($i % 5))можно написать $((i % 5)).
G-Man говорит: «Восстановите Монику»

4

POSIXly:

i=370
while [ 3500 -gt "$i" ]
do    echo "$((i+=5))"
done

...или...

echo 'for(x=370;x<=3500;x+=5)x' |bc

Я не знаю, почему ты сделал это по-другому. За исключением, конечно ...

seq 375 5 3500

... или с dc:

echo '370[5+pd3500>p]splpx'|dc

Мне интересно, как dcработает код. Это, конечно, более интригующе, чем использование seq.
дхаг

1
@dhag - пишет маленький цикл он sсохраняет [строку ]в верхней части pмассива, затем lвозвращает ее обратно в начало стека и xизвлекает ее как макрос. Внутри макроса мы помещаем 5стек в стек, затем извлекаем его, а второй сверху и +их, заменяя оба значения стека их суммой, которая pнабирается и dдублируется до того , как она помещается 3500в стек, когда мы вытолкаем его и дупе , который мы только что сделали для сравнение >. Если 3500больше, мы загружаем и выполняем в качестве макроса строку, хранящуюся в pмассиве (наш текущий макрос) , или, если не, разбиваем.
mikeserv

3

Если вы застряли на Bash 3:

echo {375..3500} | tr ' ' '\n' | sed -n 'p;n;n;n;n'

и если вы предпочитаете awk:

echo {375..3500} | tr ' ' '\n' | awk '!((NR-1)%5)'

Я не знал о расширении скобок - это действительно круто.

Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.