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

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

Примечание: Спасибо Джеффу Шаллеру и 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 } }'.

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

Например (используя $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) Авторские права (C) 2007 Free Software Foundation, Inc.

решение1

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

Thearray(курсив) в данном случае является частьюsubshell(смелый).

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

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

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

  • $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] {

}

Или согласно инструкции.

Мы можем начать сТрубопроводы

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

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

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

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

Он не может повлиять: поэтому он не может установиться.

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

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

решение2

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

# 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

Связанный контент