BASH: использование awk для фильтрации уникальных строк приводит к 0 массиву длины

Примечание: Спасибо Джеффу Шаллеру и steeldriver. Но поскольку ни один из них не был отправлен как ответ, я не уверен, как обозначить, как разрешено. Теперь я лучше понимаю трубы / подоболочки. Я почти уверен, что когда-то знал это, но прошло много времени с тех пор, как я попробовал что-нибудь сложное в bash.

Для меня работали как присвоение фильтрованного результата от awk переменной, так и замена процесса . Мой последний код для чтения несортированных уникальных строк из stdin :

 while read -r FILE do ... done < <(awk '!x[$0]++') 

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

ОРИГИНАЛЬНЫЙ ВОПРОС:

Я искал сайт, но я не могу найти ответ на свою проблему.

Я строю массив из stdin и должен фильтровать для уникальных строк. Для этого я использую awk '!x[$0]++' который я прочитал, сокращенно:

awk 'BEGIN { while (getline s) { if (!seen[s]) print s; seen[s]=1 } }' awk 'BEGIN { while (getline s) { if (!seen[s]) print s; seen[s]=1 } }' .

Фильтр работает по желанию, но проблема заключается в том, что результирующий массив из цикла while остается пустым.

Например (используя $list как суррогат для stdin ):

 list=$'red apple\nyellow banana\npurple grape\norange orange\nyellow banana' while read -r line; do array[count++]=$line done <<< "$list" echo "array length = ${#array[@]}" counter=0 while [ $counter -lt ${#array[@]} ]; do echo ${array[counter++]} done 

производит:

 array length = 5 red apple yellow banana purple grape orange orange yellow banana 

Но фильтрация $list с awk:

 list=$'red apple\nyellow banana\npurple grape\norange orange\nyellow banana' awk '!x[$0]++' <<< "$list" | while read -r line; do array[count++]=$line done echo "array length = ${#array[@]}" counter=0 while [ $counter -lt ${#array[@]} ]; do echo ${array[counter++]} done 

производит:

array length = 0

Но вывод awk '!x[$0]++' <<< "$list" выглядит нормально:

 red apple yellow banana purple grape orange orange 

Я попытался проверить каждую строку в цикле while read :

 list=$'red apple\nyellow banana\npurple grape\norange orange\nyellow banana' i=0 awk '!x[$0]++' <<< "$list" | while read -r line; do echo "line[$i] = $line" let i=i+1 done 

и он выглядит прекрасным:

 line[0] = red apple line[1] = yellow banana line[2] = purple grape line[3] = orange orange 

Что мне здесь не хватает?

В случае, если это важно, я использую bash 3.2.57:

GNU bash, версия 3.2.57 (1) -release (x86_64-apple-darwin15) Copyright (C) 2007 Free Software Foundation, Inc.

 awk '! x [$ 0] ++' <<< "$ list" |  при чтении строки -r;  делать
     array [count ++] = $ line 
  сделанный

array ( курсив ) в этом случае является частью subshell ( полужирным шрифтом ).

$line и $array имеют значение, в то время как подоболочка живая, так сказать.

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

В этом случае:

  • $array удален,
  • $line удалена.

Попробуй это:

 list=$'red apple\nyellow banana\npurple grape\norange orange\nyellow banana' awk '!x[$0]++' <<< "$list" | while read -r line; do array[count++]=$line printf "array[%d] { %s\n" ${#array[@]} # array[num_of_elements] { printf " %s\n" "${array[@]}" # elements printf "}\n" # } end of array done printf "\n[ %s ]\n\n" "END OF SUBSHELL (PIPE)" printf "array[%d] {\n" ${#array[@]} printf " %s\n" "${array[@]}" printf "}\n" 

Урожайность:

 array[1] { red apple } array[2] { red apple yellow banana } array[3] { red apple yellow banana purple grape } array[4] { red apple yellow banana purple grape orange orange } [ END OF SUBSHELL (PIPE) ] array[0] { } 

Или в соответствии с руководством.

Мы можем начать с Pipelines

[…] Каждая команда в конвейере выполняется в своей собственной подоболочке (см. Среда выполнения команд ). […]

И среда выполнения команд расширяет приключение следующим образом:

[…] Команда, вызываемая в этой отдельной среде, не может повлиять на среду выполнения оболочки.

Подстановка команд, команды, сгруппированные в круглые скобки и асинхронные команды, вызывается в среде подсетей, которая является дубликатом среды оболочки, за исключением того, что ловушки, пойманные оболочкой, сбрасываются до значений, которые оболочка унаследовала от своего родителя при вызове. Встроенные команды, которые вызывается как часть конвейера, также выполняются в среде подсетей. Изменения, внесенные в среду подсетей, не могут повлиять на среду выполнения оболочки. […]

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

Однако мы можем перенаправить и сделать что-то в направлении:

 list=$'red apple\nyellow banana\npurple grape\norange orange\nyellow banana' while read -r line; do arr[count++]=$line done <<<"$(awk '!x[$0]++' <<< "$list")" echo "arr length = ${#arr[@]}" count=0 while [[ $count -lt ${#arr[@]} ]]; do echo ${arr[count++]} done 

Некоторые решения вашей проблемы без цикла

 # use bash's mapfile with process substitution mapfile -t arr < <( awk '!x[$0]++' <<<"$list" ) # use array assignment syntax (at least bash, ksh, zsh) # of a command-substituted value split at newline only # and (if the data can contain globs) globbing disabled set -f; IFS='\n' arr=( $( awk '!x[$0]++' <<<"$list" ) ); set +f