Bash: как удалить элементы из массива на основе шаблона - PullRequest
18 голосов
/ 26 августа 2010

Скажем, у меня есть массив bash (например, массив всех параметров), и я хочу удалить все параметры, соответствующие определенному шаблону, или альтернативно скопировать все оставшиеся элементы в новый массив.В качестве альтернативы, наоборот, сохраняйте элементы соответствующими шаблону.

Пример для иллюстрации:

x=(preffoo bar foo prefbaz baz prefbar)

, и я хочу удалить все, начиная с pref, чтобы получить

y=(bar foo baz)

(порядок не релевантен)

Что если я захочу сделать то же самое для списка слов, разделенных пробелом?

x="preffoo bar foo prefbaz baz prefbar"

и снова удалить все, начинаяс pref, чтобы получить

y="bar foo baz"

Ответы [ 5 ]

11 голосов
/ 02 ноября 2016

Фильтрация массива довольно сложна, если учесть возможность элементов, содержащих пробелы (не говоря уже о «странных» символах).В частности, ответы, данные до сих пор (относящиеся к различным формам ${x[@]//pref*/}), потерпят неудачу с такими массивами.

Я несколько исследовал эту проблему и нашел решение, однако это не является хорошим однострочником.Но, по крайней мере, так.

Для иллюстративных примеров предположим, что arr называет массив, который мы хотим отфильтровать.Начнем с основного выражения:

for index in "${!ARR[@]}" ; do [[ …condition… ]] && unset -v 'ARR[$index]' ; done
ARR=("${ARR[@]}")

Уже есть несколько элементов, о которых стоит упомянуть:

  1. "${!ARR[@]}" вычисляет индексы массива (в отличие от элементов).
  2. Форма "${!ARR[@]}" обязательна.Вы не должны пропускать кавычки или изменять @ на *.В противном случае выражение будет разбиваться на ассоциативных массивах, где ключи содержат пробелы (например).
  3. Часть после do может быть любой, какой вы захотите.Идея состоит лишь в том, что вы должны сделать unset, как показано для элементов, которые вы не хотите иметь в массиве.
  4. Рекомендуется или даже необходимо , чтобы использовать -v и кавычки с unset, иначе могут произойти плохие вещи.
  5. Если часть после do соответствует предложенной выше, вы можете использовать && или ||, чтобы отфильтровать элементы, которые либоПройдите или не выполните условие.
  6. Вторая строка, переназначение ARR, необходима только для неассоциативных массивов, а прервется с ассоциативными массивами .(Я не быстро придумал универсальное выражение, которое будет обрабатывать оба, пока мне не нужно…).Для обычных массивов это необходимо, если вы хотите иметь последовательные индексы.Поскольку unset в элементе массива не изменяет (опускает на единицу) элементы более высоких индексов - он просто делает дыру в индексах.Теперь, если вы только перебираете массив (или расширяете его целиком), это не проблема.Но для других случаев вам нужно переназначить индексы.Также обратите внимание, что если у вас есть дыра в индексах, прежде чем она будет также удалена.Поэтому, если вам необходимо сохранить существующие дыры, необходимо выполнить больше логики, кроме unset и окончательного переназначения.

Теперь, когда дело доходит до условия.Выражение [[ ]] - это простой способ, если вы можете его использовать.(См. здесь .) В частности, он поддерживает сопоставление регулярных выражений с использованием Расширенные регулярные выражения .(См. здесь .) Также будьте осторожны с использованием grep или любого другого линейного инструмента для этого, если вы ожидаете, что элементы массива могут содержать не только пробелы, но и новые строки.(Хотя я думаю, что очень неприятное имя файла может иметь символ новой строки…)


Если обратиться к самому вопросу, выражение [[ ]] должно быть:

[[ ${ARR[$index]} =~ ^pref ]]

&& unset как указано выше)


Давайте, наконец, посмотрим, как это работает с этими трудными случаями.Сначала мы создаем массив:

declare -a ARR='([0]="preffoo" [1]="bar" [2]="foo" [3]="prefbaz" [4]="baz" [5]="prefbar" [6]="pref with spaces")'
ARR+=($'pref\nwith\nnew line')
ARR+=($'\npref with new line before')

. Мы можем увидеть, что у нас есть все сложные случаи, запустив declare -p ARR и получив:

declare -a ARR='([0]="preffoo" [1]="bar" [2]="foo" [3]="prefbaz" [4]="baz" [5]="prefbar" [6]="pref with spaces" [7]="pref
with
new line" [8]="
pref with new line before")'

Теперь запустим выражение фильтра:

for index in "${!ARR[@]}" ; do [[ ${ARR[$index]} =~ ^pref ]] && unset -v 'ARR[$index]' ; done

и другой тест (declare -p ARR) дает ожидаемый результат:

declare -a ARR='([1]="bar" [2]="foo" [4]="baz" [8]="
pref with new line before")'

обратите внимание, как были удалены все элементы, начиная с pref, но индексы не изменились.Также обратите внимание, что ${ARRAY[8]} все еще там, поскольку он начинается с новой строки, а не pref.

Теперь для окончательного переназначения:

ARR=("${ARR[@]}")

и проверки (declare -p ARR):

declare -a ARR='([0]="bar" [1]="foo" [2]="baz" [3]="
pref with new line before")'

, что именно то, что ожидалось.


Для заключительных нот.Было бы хорошо, если бы это могло быть изменено на гибкую однострочную.Но я не думаю, что есть способ сделать его короче и проще, как сейчас, без определения функций или тому подобного.

Что касается функции, то было бы неплохо, чтобы она принимала массив, возвращала массиви легко настроить тест, чтобы исключить или сохранить.Но я не достаточно хорош с Башом, чтобы сделать это сейчас.

10 голосов
/ 27 августа 2010

Другой способ вырезать плоскую строку - преобразовать ее в массив, а затем использовать метод массива:

x="preffoo bar foo prefbaz baz prefbar"
x=($x)
x=${x[@]//pref*}

Сравните это с началом и окончанием с массивом:

x=(preffoo bar foo prefbaz baz prefbar)
x=(${x[@]//pref*})
7 голосов
/ 27 августа 2010

Чтобы удалить плоскую строку (Халк уже дал ответ для массивов), вы можете включить опцию оболочки extglob и запустить следующее расширение

$ shopt -s extglob
$ unset x
$ x="preffoo bar foo prefbaz baz prefbar"
$ echo ${x//pref*([^ ])?( )}
bar foo baz

Параметр extglob необходим для форм *(pattern-list) и ?(pattern-list). Это позволяет вам использовать регулярные выражения (хотя и в форме, отличной от большинства регулярных выражений), а не просто расширение пути (*?[).

Ответ, который дал Халк для массивов, будет работать только для массивов. Если кажется, что он работает с плоскими строками, это только потому, что при тестировании массив не был сначала сброшен.

, например

$ x=(preffoo bar foo prefbaz baz prefbar)
$ echo ${x[@]//pref*/}
bar foo baz
$ x="preffoo bar foo prefbaz baz prefbar"
$ echo ${x[@]//pref*/}
bar foo baz
$ unset x
$ x="preffoo bar foo prefbaz baz prefbar"
$ echo ${x[@]//pref*/}

$
6 голосов
/ 26 августа 2010

Вы можете сделать это:

Удалить все вхождения подстроки.

# Not specifing a replacement defaults to 'delete' ...
echo ${x[@]//pref*/}      # one two three four ve ve
#               ^^          # Applied to all elements of the array.

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

Для пробелов это тоже самое

x="preffoo bar foo prefbaz baz prefbar"
echo ${x[@]//pref*/}

Вывод:

bar foo baz

1 голос
/ 29 июня 2015

Я определил и использовал следующую функцию:

# Removes elements from an array based on a given regex pattern.
# Usage: filter_arr pattern array
# Usage: filter_arr pattern element1 element2 ...
filter_arr() {  
    arr=($@)
    arr=(${arr[@]:1})
    dirs=($(for i in ${arr[@]}
        do echo $i
    done | grep -v $1))
    echo ${dirs[@]}
}

Пример использования:

$ arr=(chicken egg hen omelette)
$ filter_arr "n$" ${arr[@]}

Выход:

яичный омлет

Выход из функции - строка. Чтобы преобразовать его обратно в массив:

$ arr2=(`filter_arr "n$" ${arr[@]}`)
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...