Как перебирать несколько диапазонов/последовательностей/числ, указанных в var?

Я могу перебирать несколько диапазонов/последовательностей/числа с помощью таких конструкций, как:

for i in $(seq 1 3) 5 $(seq 7 9) 11; do echo $i; done
for i in {1..3} 5 {7..9} 11; do echo $i; done

Но как добиться того же результата, если в var указаны диапазоны/числа:

var = "{1..3} 5 {7..9} 11"

Я обнаружил, что это можно сделать с помощью eval и seq:

var = "seq 1 3; echo 5; seq 7 9; echo 11"; for i in $(eval "$var"); do echo $i; done

Здесь показано, что eval echo позволяет использовать диапазоны в таких переменных, как (гораздо удобнее, чем предыдущий):

var = "{1..3} 5 {7..9} 11"; for i in $(eval echo "$var"); do echo $i; done

Можно ли как-то добиться того же результата, не используя eval? Или использовать eval в данном конкретном случае можно?


Обновлять

Поскольку существуют дополнительные накладные расходы при синтаксическом анализе диапазонов/числа, определенных в простой строковой переменной, и поскольку я могу контролировать формирование такого параметра, также стоит рассмотреть решения для входного массива var, определенного следующим образом:

ranges=( '1 3' 5 '7 9' 11 )

Или еще более прямолинейно и просто для реализации (но немного избыточно в выражении) с использованием диапазонов вместо чисел:

ranges=( '1 3' '5 5' '7 9' '11 11' )

В случае использования массива с диапазонами в качестве входной строки этот вопрос становится довольно простым и сводится к двойному циклу по массиву с диапазонами и по каждому диапазону этого массива.

При указании диапазонов в виде массива не будет необходимости использовать eval или анализировать строку с диапазонами в массив (что имеет ту же самую, но скрытую опасность eval).

Попробуйте: var=({1..3} 5 {7..9} 11);for i in "${var[@]}";do echo $i;done

F. Hauri - Give Up GitHub 15.06.2024 16:01

Вы могли бы var = "{1..3} 5 {7..9} 11"; declare -a "array=($var)", но ваша переменная все еще eval устарела.

F. Hauri - Give Up GitHub 15.06.2024 16:07
Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
2
2
107
3
Перейти к ответу Данный вопрос помечен как решенный

Ответы 3

Ответ принят как подходящий

1. Первый ответ...

Вы можете использовать:

var = "{1..3} 5 {7..9} 11"

declare -a "array=($var)"
for i in "${array[@]}"; do
    echo $i
done
1
2
3
5
7
8
9
11

Но будьте осторожны!! Эта работа похожа на eval:

var='{1..3} $(uptime) 6 7'
echo "$var"
{1..3} $(uptime) 6 7
declare -a "array=($var)"
for i in "${array[@]}"; do
    echo $i
done
1
2
3
16:36:29
up
22
days,
4:54,
23
users,
load
average:
2.48,
1.89,
1.83
6
7

Обратите внимание, что использование двойных кавычек:

var='{1..3} "$(uptime)" 6 7'

Будет более читабельно:

1
2
3
 16:41:03 up 22 days, 4:59, 23 users, load average: 1.47, 2.09, 1.98
6
7

О:

Или использование eval в данном конкретном случае нормально?

Это ваша личная ответственность! Если вы знаете, что делаете, и уверены в том, откуда приходит контент, тогда все в порядке.

2. Без $var у вас будут циклы do.

Если вы хотите избежать eval, вы можете использовать такую ​​функцию, как:

readRange() { # Usage: readRange "$var" <arrayName>
    local -a arin
    local elem
    local -i iter start end
    local -n arout=$2
    arout=()
    read -ra arin <<< "$1"
    for elem in "${arin[@]}"; do
        case $elem in
            '{'[0-9]*'..'*[0-9]'}' )
                IFS='.{}' read -r _ start _ end <<< "$elem"
                for ((iter=start; iter<=end; iter++)); do
                    arout+=($iter)
                done
            ;;
            *[^0-9]* )
                echo ERROR 1>&2
                return -1
            ;;
            *)
                arout+=($elem)
            ;;
        esac
    done
}

var = "{1..3} 5 {7..9} 11"
readRange "$var" resultArray
for i in "${resultArray[@]}"; do
    echo "$i"
done
1
2
3
5
7
8
9
11

3. Или использовать eval:

readRange() { # Usage: readRange "$var" <arrayName>
    local -a arin
    local elem start end
    local -n arout=$2
    arout=()
    read -ra arin <<< "$1"
    for elem in "${arin[@]}"; do
        read -r start end <<< ${elem//[^0-9]/ }
        if [[ -n $end ]]; then
            arout+=($(seq $start $end))
        else
            arout+=($start)
        fi
    done
}

var = "{1..3} 5 {7..9} 11"
readRange "$var" resultArray
for i in "${resultArray[@]}"; do
    echo "$i"
done
1
2
3
5
7
8
9
11

Без eval и без каких-либо переменных, доступных оболочке для расширения, подстановки, разделения слов и т. д.:

$ cat tst.sh
#!/usr/bin/env bash

ranges=( '1 3' 5 '7 9' 11 )
for range in "${ranges[@]}"; do
    printf '\nrange = %s\n' "$range"
    read -r -a beg_end <<< "$range"
    seq "${beg_end[0]}" "${beg_end[-1]}"
done

$ ./tst.sh

range = 1 3
1
2
3

range = 5
5

range = 7 9
7
8
9

range = 11
11

Если во входных данных было что-то, что не было допустимым диапазоном, то seq в приведенном выше примере не будет работать:

$ cat tst.sh
#!/usr/bin/env bash

ranges=( '1 3' '$(date)' '7 9' 11 )
for range in "${ranges[@]}"; do
    printf '\nrange = %s\n' "$range"
    read -r -a beg_end <<< "$range"
    seq "${beg_end[0]}" "${beg_end[-1]}"
done

$ ./tst.sh

range = 1 3
1
2
3

range = $(date)
seq: invalid floating point argument: ‘$(date)’
Try 'seq --help' for more information.

range = 7 9
7
8
9

range = 11
11

Если вы хотите решить эту проблему каким-то другим способом, есть несколько альтернатив, но лично я бы все равно просто позволил seq быть тем, кто обнаруживает проблему, а затем просто добавил бы любую обработку, которую я хотел, например. чтобы напечатать сообщение об ошибке, а затем остановить обработку:

$ cat tst.sh
#!/usr/bin/env bash

ranges=( '1 3' '$(date)' '7 9' 11 )
for range in "${ranges[@]}"; do
    printf '\nrange = %s\n' "$range"
    read -r -a beg_end <<< "$range"
    seq "${beg_end[0]}" "${beg_end[-1]}" 2>/dev/null || {
        printf 'Bad range: "%s", exiting\n' "$range" >&2
        exit 1
    }
done

$ ./tst.sh

range = 1 3
1
2
3

range = $(date)
Bad range: "$(date)", exiting

Я использую '$(date)' как «плохое» значение выше, чтобы вы могли сравнить, как это решение справляется с потенциальной атакой путем внедрения команд (рассматривая его как просто буквальную строку и не выполняя его как команду) с eval или любым другим решением, которое вы можете обдумывать.

Проблема с использованием расширений фигурных скобок, таких как {1..3}, хранящихся в переменной, заключается в том, что раскрытие скобок происходит до раскрытия параметра (переменной) (см. https://www.gnu.org/software/bash/manual/html_node/Shell-Expansions .html), поэтому невозможно var='{1..3}' расширить до 1 2 3 без двух проходов расширения с использованием eval или аналогичного, поэтому переменная расширяется до {1..3} на первом проходе перед оболочкой на втором проходе, а затем расширяет это выражение в скобках при вызове команды, которую вы хочу работать на результат. Пока вы полностью контролируете содержимое переменной, содержащей диапазон, который вы хотите расширить, eval или переменная без кавычек будет безопасной в этом конкретном случае, но нет необходимости полагаться на это, как я показываю выше.

Чтобы создать массив ranges на основе строки var: var = "'1 3' 5 '7 9' 11"; declare -a "ranges=($var)"

Anton Samokat 15.06.2024 18:29

@AntonSamokat Я бы посоветовал вам не делать этого, поскольку это не лучше, чем использование eval, оно все равно расширит любую переменную в var, включая выполнение любой команды в ней. Попробуйте var = "'1 3' $(date) '7 9' 11"; declare -a "ranges=($var)"; declare -p ranges или даже var = "'1 3' '$(date)' '7 9' 11"; declare -a "ranges=($var)"; declare -p ranges и обратите внимание, что выходные данные содержат результат выполнения date вместо строки $(date), что является проблемой eval, которую вы пытаетесь избежать. Не пытайтесь избежать eval, используя что-то другое с той же проблемой, но с более непонятным синтаксисом.

Ed Morton 15.06.2024 18:53

Я добавил потенциальную атаку с внедрением команд в пример в своем ответе, чтобы вы могли увидеть, как мой код с ней справляется. У вас могут возникнуть аналогичные проблемы в других решениях с любым потенциальным расширением переменной (например, '$HOME') или подстановочными символами (например, '*'), которые мой ответ снова будет рассматривать как буквальные строки.

Ed Morton 15.06.2024 19:10

Я понимаю. Да, это даже хуже, чем eval из-за такой скрытой опасности, о которой известно eval. Спасибо. Оказывается, в этом случае вместо использования строковой var с диапазонами целесообразнее использовать массив var с диапазонами. И между функциями будет передаваться массив, а не строка.

Anton Samokat 15.06.2024 19:12

Запускать seq N N для печати N — это излишне! Вместо этого вы можете использовать всего лишь один тест echo!

F. Hauri - Give Up GitHub 02.07.2024 15:11

@F.Hauri-GiveUpGitHub, то вы добавляете тест, который будет выполняться для каждого входного «диапазона», чтобы избежать ненужного вызова seq для некоторых входных диапазонов. Это того не стоит, если нет большого количества входных диапазонов с высоким процентом одиночных значений (это может замедлить обработку, если отдельные значения встречаются редко). Как всегда, не оптимизируйте производительность до тех пор, пока у вас не появится причина и вы не поймете, какие изменения действительно помогут. В этом случае, если бы ОП решил проверить отдельные значения, это, очевидно, была бы тривиальная настройка, как вы упомянули.

Ed Morton 02.07.2024 15:22

Если возможно, я бы пропустил простую скалярную строковую переменную и просто использовал массив. Вы уже это делали, но если возможно, удалите жесткие строки, объявляющие диапазоны, и просто объявите диапазоны в самом определении массива.

$: lst=( {1..3} 5 {7..9} 11 )
$: printf " %s" "${lst[@]}" $'\n'
 1 2 3 5 7 8 9 11

Если это не вариант, то, как уже было сказано, вы можете использовать declare -

$: var = "{1..3} 5 {7..9} 11"
$: declare -a "lst=( $var )"
$: printf " %s" "${lst[@]}" $'\n'
 1 2 3 5 7 8 9 11

Но следите за вредоносными встроенными командами в ваши данные, как показано .

Если ваши последовательности невелики, вы можете хранить всю последовательность, а не только «контрольные» данные.

$: lst=( {1..3} 5 {7..9} 11 )  # create the set
$: seq = "${lst[@]}"             # store as flat string
$: unset lst                   # delete it, or go out of scope
$: echo "[$seq][${lst[@]:-}]"  # string has it, array does not
[1 2 3 5 7 8 9 11][]
$: lst=( $seq )                # re-create the array - insecure
$: for n in "${lst[@]}"; do echo $n; done
1
2
3
5
7
8
9
11

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

Это интересный шаг вперед. Я подумал, что было бы хорошо использовать массивы массивов, чтобы исключить необходимость парсить строки с диапазонами. Но ush не поддерживает многомерные массивы . Проверю это. Спасибо!

Anton Samokat 17.06.2024 20:35

Верно — bash не поддерживает многомерные массивы, но поддерживает ассоциативные массивы с произвольными строками в качестве ключей, которые могут быть ключами, которые вы бы использовали при многомерном поиске с любым разделителем, который не будет частью какого-либо ключа — ${table["1,3,15"]} работает просто хорошо.

Paul Hodges 17.06.2024 22:50

Другие вопросы по теме