Использование подстановки параметров в массиве Bash

8

У меня есть file.txt, который мне нужно прочитать в массив Bash. Затем мне нужно удалить пробелы, двойные кавычки и все, кроме первой запятой в каждой записи . Вот как далеко я продвинулся:

$ cat file.txt
10,this
2 0 , i s
30,"all"
40,I
50,n,e,e,d,2
60",s e,e"

$ cat script.sh
#!/bin/bash
readarray -t ARRAY<$1
ARRAY=( "${ARRAY[@]// /}" )
ARRAY=( "${ARRAY[@]//\"/}" )
for ELEMENT in "${ARRAY[@]}";do
    echo "|ELEMENT|$ELEMENT|"
done

$ ./script.sh file.txt
|ELEMENT|10,this|
|ELEMENT|20,is|
|ELEMENT|30,all|
|ELEMENT|40,I|
|ELEMENT|50,n,e,e,d,2|
|ELEMENT|60,se,e|

Который прекрасно работает, за исключением ситуации с запятой. Я знаю, что есть несколько способов сделать скин для этого кота, но из-за большого скрипта, частью которого я являюсь, я действительно хотел бы использовать подстановку параметров, чтобы добраться сюда:

|ELEMENT|10,this|
|ELEMENT|20,is|
|ELEMENT|30,all|
|ELEMENT|40,I|
|ELEMENT|50,need2|
|ELEMENT|60,see|

Это возможно с помощью замены параметров?

Джон Ред
источник
3
Есть ли какая-то причина, по которой вам нужно хранить текст в массиве, и почему вы не можете, например, разрешить awkили sedвыполнить обработку данных?
Кусалананда
@Jeff - цикл по массиву станет кошмаром для реализации в более широком сценарии, над которым я работаю.
Джон Ред
3
@JonRed Я не знаю, что вы делаете, поэтому вполне возможно, что у вас может не быть выбора в этом вопросе, но обычно, когда вы обнаруживаете, что делаете такие сложные струнные акробатики в оболочке, это очень хороший признак того, что вы следует использовать реальный язык программирования. Оболочка не предназначена для использования в качестве языка программирования, и хотя она может использоваться как единое целое, на самом деле она не является хорошей идеей для более сложных вещей. Я настоятельно призываю вас рассмотреть возможность перехода на Perl или Python или любой другой язык сценариев.
Тердон
@terdon Забавно, я только что сказал своему коллеге почти то же самое, прежде чем прочел этот пост. Я в основном сказал, что это финальная версия этого скрипта и что для любых дальнейших требований потребуется переписать на Perl. Так что да, я определенно согласен
Джон Ред

Ответы:

9

Я бы удалил то, что нужно удалить, используя sed перед загрузкой в ​​массив (также обратите внимание на имена переменных в нижнем регистре, в общем случае лучше избегать заглавных букв в сценариях оболочки):

#!/bin/bash
readarray -t array< <(sed 's/"//g; s/  *//g; s/,/"/; s/,//g; s/"/,/' "$1")
for element in "${array[@]}";do
    echo "|ELEMENT|$element|"
done

Это приводит к следующему выводу вашего файла примера:

$ foo.sh file 
|ELEMENT|10,this|
|ELEMENT|20,is|
|ELEMENT|30,all|
|ELEMENT|40,I|
|ELEMENT|50,need2|
|ELEMENT|60,see|

Если вы действительно должны использовать подстановку параметров, попробуйте что-то вроде этого:

#!/bin/bash
readarray -t array< "$1"
array=( "${array[@]// /}" )
array=( "${array[@]//\"/}" )
array=( "${array[@]/,/\"}" )
array=( "${array[@]//,/}" )
array=( "${array[@]/\"/,}" )

for element in "${array[@]}"; do
    echo "|ELEMENT|$element|"
done
Тердон
источник
1
@JonRed Я добавил версию с подстановкой параметров, но она сложная, громоздкая и безобразная. Делать подобные вещи в оболочке очень редко хорошая идея.
Тердон
1
Обратите внимание, что если вы удалили как пробелы, так и двойные кавычки, эти символы будут доступны вместо ваших RANDOMTEXTTHATWILLNEVERBEINTHEFILE.
Кусалананда
1
@Kusalananda, да, я только что прочитал твой ответ. Надо было подумать об этом! Спасибо :)
Terdon
Непосредственно отвечает на вопрос, иллюстрирует, почему мое предпочтительное решение не является идеальным, и предоставляет наиболее жизнеспособную альтернативу. Ты выиграл, лучший ответ.
Джон Ред
10

Насколько я вижу, нет необходимости читать его в bashмассив для создания этого вывода:

$ sed 's/[ "]//g; s/,/ /; s/,//g; s/ /,/; s/.*/|ELEMENT|&|/' <file
|ELEMENT|10,this|
|ELEMENT|20,is|
|ELEMENT|30,all|
|ELEMENT|40,I|
|ELEMENT|50,need2|
|ELEMENT|60,see|

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

В качестве альтернативы, с GNU sed:

sed 's/[ "]//g; s/,//2g; s/.*/|ELEMENT|&|/' <file

(стандарт sedне поддерживает комбинацию 2и gкак флаги для sкоманды).

Кусалананда
источник
1
с GNU sed вы можете использовать 's/,//2gдля удаления запятых, начиная со 2-го
Гленн Джекман
2
И последние 2 с /// команды могут быть, s/.*/|ELEMENT|&|/но это может быть больше усилий для sed.
Гленн Джекман
1
@glennjackman Возможно, но выглядит довольно аккуратно.
Кусалананда
Да, это часть более крупного сценария. Массив необходим не только для вывода. Отсюда мой интерес к замене параметров. Я мог бы зациклить массив с этим, но это будет кошмар для реализации. Терндон предоставил решение без петель, использующее sed, к которому я, скорее всего, вернусь, если замена параметров не нужна.
Джон Ред
Однако, если бы я не был привязан к использованию массива, это было бы лучшим решением.
Джон Ред
9
ELEMENT='50,n,e,e,d,2'
IFS=, read -r first rest <<<"$ELEMENT"
printf "%s,%s\n" "$first" "${rest//,/}"
50,need2

Избавьтесь от привычки использовать имена переменных ALLCAPS. В конечном итоге вы столкнетесь с критической «системной» переменной, такой как PATH, и сломаете свой код.

Гленн Джекман
источник
Не подстановка параметров. НО, я не знал, что имена переменных ALLCAPS были плохой привычкой в ​​Bash. Вы делаете хорошую мысль, которую поверхностный поиск в Google определенно подтверждает. Спасибо за улучшение моего стиля! :)
Джон Ред
1
Я отвечаю на вопросы, где человек написал, PATH=something; ls $PATHа потом задумался об ls: command not foundошибке.
Гленн Джекман
1
Есть почти сотня встроенных переменных, которые названы во всех заглавных буквах (нажмите на ссылку этой страницы
Джефф Шаллер
8

[Это, по сути, более полная версия ответа Гленна Джекмана ]

Создание ассоциативного массива из разделенного ключа и значения с использованием первой запятой в качестве разделителя:

declare -A arr
while IFS=, read -r k v; do arr["${k//[ \"]}"]="${v//[ ,\"]}"; done < file.txt
for k in "${!arr[@]}"; do 
  printf '|ELEMENT|%s,%s|\n' "$k" "${arr[$k]}"
done
|ELEMENT|20,is|
|ELEMENT|10,this|
|ELEMENT|50,need2|
|ELEMENT|40,I|
|ELEMENT|60,see|
|ELEMENT|30,all|
steeldriver
источник
6

Вы можете перебрать массив и использовать промежуточную переменную:

for((i=0; i < "${#ARRAY[@]}"; i++))
do
  rest="${ARRAY[i]#*,}"
  ARRAY[i]="${ARRAY[i]%%,*}","${rest//,/}"
done

Это присваивается restчасти после первой запятой; затем мы объединяем три части обратно в исходную переменную:

  • часть перед первой запятой
  • запятая
  • замена restкаждой запятой ни с чем
Джефф Шаллер
источник
Это была моя первая мысль, и она достаточно проста для примера, но это часть более крупного сценария, где массив массивен, и в нем уже есть циклы, и это будет целое. Это определенно сработало бы, но было бы очень громоздким для реализации в более крупном проекте, над которым я работаю.
Джон Ред
1
Справедливо; Я просто попытался ответить в рамках ограничений (только расширение параметров).
Джефф Шаллер