Итак, во-первых, как кратко обсуждалось здесь общая форма...
for arg in {brace..expanded..set}; do ...
... является, на мой взгляд, примером ошибки программирования оболочки. Она генерирует формульный набор в цикле, сохраняет каждый результат в разграниченном массиве, затем передает другому циклу этот массив в качестве списка для итерации.
Как правило, расширение скобок не особенно полезно в контексте программирования, потому что все программирование оболочки так или иначе макросоподобно, а расширение скобок уже является макросом. Хотя brace-expansion является (также по моему мнению) очень полезной интерактивной функцией оболочки именно потому, что она является макросом, в остальном она, вероятно, расточительна.
Brace-расширение также не является переносимым (читай: POSIX-переносимым). Обычно вы можете рассчитывать на его работу в текущих версиях zsh
, bash
, yash
и ksh93
, но не в других, насколько я знаю. И даже в этих оболочках он может быть отключен с помощью опций оболочки, поэтому на него нельзя однозначно полагаться в контексте функций оболочки, хотя его наличие обычно можно проверить в $-
или в выводе set +o
. Между прочим, каждая из вышеупомянутых оболочек требует различных типов тестов, поэтому если вы планируете использовать его в функции и хотите проверить его наличие, обратитесь к руководству по вашей целевой оболочке.
В других оболочках вы можете получить практически такое же поведение - за исключением того, что для генерации набора необходимо вызвать внешний исполняемый файл - разделив вывод команды-замены на $IFS
. При наличии программы seq
это легко продемонстрировать, например...
unset IFS
### ^ensure $IFS default behavior
for arg in $(seq 7000)
do printf %d\\n "$arg"
done
... или...
unset IFS
printf %d\\n $(seq 7000)
... но в написанном виде ни то, ни другое не дает ничего, кроме недостатков по сравнению с...
seq 7000
Тем не менее - для первого примера - метод почти такой же, как и версия с расширенными скобками. В обоих случаях какая-то другая функция или процесс генерирует целый список аргументов, которые затем итерирует управляющая конструкция for
. В последнем случае, однако, (для оболочек, отличных от ksh93
) добавляются накладные расходы на открытие/исчезновение трубы, вилочный процесс оболочки и его дочерний процесс seq
, и, наконец, работа по разделению его вывода на $IFS
.
$IFS
Расщепление, кстати, происходит с очень разной скоростью в зависимости от используемой оболочки. bash
, например, обычно плохо справляется с простой $IFS
разбивкой по производительности, тогда как dash
- в зависимости от длины строки для разбивки строк - может делать это так же быстро, как некоторые другие оболочки делают brace-расширение.
Независимо от того, используете ли вы brace-расширение или $IFS
разбиение, конечным результатом будет запуск некоторого цикла для создания формульного списка, а затем другого для итерации этого списка. Что вы могли - и, вероятно, должны были - сделать вместо этого, так это запустить один цикл для применения формулы, которая генерирует список, избавиться от использованных итераторов как можно быстрее и проверить результат каждой итерации на удовлетворительный результат. Именно такие вещи вы могли бы сделать на языке C, например...
for ( x = 0; x < 10; x++ ){ ...
Это практически тот же синтаксис, который вы можете использовать в bc
. И он очень похож на формы удобного синтаксиса, которые можно использовать в ksh93
, zsh
или bash
, например:
for (( x=0; x<10; x++ )); do ...
Это, однако, также не переносимо (опять же, читайте: POSIX). Вместо этого можно сделать:
iterator=$(( start_value - interval ))
while [ "$(( iterator += interval ))" -le "$end_value" ]; do ...
...или...
iterator=$(( start_value - interval ))
while [ "$(( ( iterator += interval ) <= end_value ))" -ne 0 ]; do ...
POSIX определяет нечто близкое к равенству возможностей для арифметических расширений оболочки и арифметических операций языка C - там, где речь идет о целых числах, то есть. Поэтому часто можно удобно и портативно итерировать даже сложные формулы с помощью только одного расширения - и даже синхронно итерировать несколько переменных одновременно.
С другой стороны, такая конструкция, скорее всего, натолкнет вас на еще одно неравенство между языком Си и языком оболочки. Чтение и запись в оболочке почти всегда буквально read()
s и write()
s - каждый сам по себе системный вызов - и в синтаксисе оболочки нет переносимого аналога fread()
, fwrite()
и/или setbuf()
. И поэтому любой цикл, выполняющий любую из этих операций за итерацию - даже если он делает это только с помощью встроенных модулей - это обязательно приведет к снижению производительности.
Однако иногда можно найти золотую середину между генерацией и работой всего набора последовательно или параллельно в сценарии оболочки.
set 0 1 2 3 4 5 6 7 8 9
for n do printf "$n%d\n" "$@"; done
Это делает десять write()
s за десять итераций и печатает разделенную новой строкой последовательность от 00 до 99. Это крошечный пример - и все еще расточительный, поскольку 30-байтовая write()
вряд ли является значительным улучшением по сравнению с 3-байтовой write()
, но он демонстрирует, какой метод вы можете использовать. Как я уже говорил, язык программирования shell очень макроподобен в том смысле, что все является строкой. И поэтому, если вы можете разбить свой набор целей на более мелкие, более управляемые наборы - и затем неоднократно обращаться к ним в контексте цикла, чтобы в конечном итоге сформировать свой набор целей, вы обычно можете получить более производительный результат.
Вот более сложный, похожий на макрос пример:
sh -c '
i=0 _i=-25
set "$1" "$1" "$1" "$1" "$1"
for n do eval "
printf %b%d $@ $@ $@ $@ $@"
done' -- \
'"\01$((1+(i<(i+=!(_i=(_i+=25)%275)))))" "$i$_i"'
который выполняет 5 итераций для одного write()
в среднем около 100 байт и печатает:
10 125 150 175 1100 1125 1150 1175 1200 1225 1250
20 225 250 275 2100 2125 2150 2175 2200 2225 2250
30 325 350 375 3100 3125 3150 3175 3200 3225 3250
40 425 450 475 4100 4125 4150 4175 4200 4225 4250
50 525 550 575 5100 5125 5150 5175 5200 5225 5250
60 625 650 675 6100 6125 6150 6175 6200 6225 6250
70 725 750 775 7100 7125 7150 7175 7200 7225 7250
80 825 850 875 8100 8125 8150 8175 8200 8225 8250
90 925 950 975 9100 9125 9150 9175 9200 9225 9250
100 1025 1050 1075 10100 10125 10150 10175 10200 10225 10250
110 1125 1150 1175 11100 11125 11150 11175 11200 11225 11250
120 1225 1250 1275
Si es "nuevo en Linux", entonces ¿por qué está usando Kali Linux ? Descubrirá que es poco probable que obtenga mucha ayuda aquí si es un principiante que intenta usar Kali , porque no es una distribución para principiantes.