Заполните массив bash из ввода, разделенного NUL

Я хочу создать массив bash из ввода, разделенного NUL (из стандартного ввода).

Вот пример:

## Let define this for clarity
$ hd() { hexdump -v -e '/1 "%02X "'; echo ;}
$ echo -en "A B\0C\nD\0E\0" | hd
41 20 42 00 43 0A 44 00 45 00

Так что это мой вклад.

Теперь работа с NUL работает нормально, если не использовать команду -a of read:

$ while read -r -d '' v; do echo -n "$v" | hd; done < <(echo -en "A B\0C\nD\0E\0")
41 20 42 
43 0A 44 
45 

Получаем правильные значения. Но я не могу сохранить эти значения, используя -a:

$ read -r -d '' -a arr < <(echo -en "A B\0C\nD\0E\0")
$ declare -p arr
declare -a arr='([0]="A" [1]="B")'

Чего я явно не хотел. Я бы хотел:

$ declare -p arr
declare -a arr='([0]="A B" [1]="C
D" [2]="E")'

Есть ли способ использовать read -a, и если он не работает, то почему? Знаете ли вы простой способ сделать это (избегая цикла while)?


person vaab    schedule 05.05.2014    source источник
comment
Зачем избегать цикла while? Цикл while одобрен FAQ, irc.freenode.org/#bash-blessed Правильный способ сделать это.   -  person Charles Duffy    schedule 05.05.2014
comment
... заметьте, я бы предпочел, чтобы readarray или mapfile поддерживали разделители NUL, но начиная с Bash 4.3 они этого не делают. Возможно, кто-то должен спросить Чета, будет ли принят патч...   -  person Charles Duffy    schedule 05.05.2014
comment
Я использую цикл while. Мне просто было интересно, почему это не сработало, и я не хотел убедиться, что я не упустил что-то очевидное. Любые детали (отчет об ошибке, ссылка на исходный код, ограничения ОС, подтверждение этого недостатка из источника), которые дадут больше информации о «почему»?   -  person vaab    schedule 05.05.2014
comment
-d предоставляет разделитель, используемый read -a, чтобы сообщить ему, когда прекратить чтение полностью, а не когда прекратить чтение отдельной записи. Делает ли это поведение более понятным?   -  person Charles Duffy    schedule 05.05.2014


Ответы (4)


read -a, как вы заметили, не подходит для этой работы; он поддерживает только разделители, отличные от NUL. Соответствующий метод описан в BashFAQ #1:

arr=()
while IFS= read -r -d '' entry; do
  arr+=( "$entry" )
done

С точки зрения почему read -d '' -a является неправильным инструментом: -d дает read аргумент, чтобы определить, когда следует полностью прекратить чтение, а не когда следует прекратить чтение отдельного элемента.

Учитывать:

while IFS=$'\t' read -d $'\n' words; do
  ...
done

... это будет читать слова, разделенные символами табуляции, пока не достигнет новой строки. Таким образом, даже с read -a использование -d '' будет читать пока не достигнет NUL.

То, что вы хотите читать до тех пор, пока содержимое не станет доступным и не будет разделено на NUL, не является «-d» NUL, а вообще не символом конца строки (и пустым IFS). Это не то, что в настоящее время read делает доступным.

person Charles Duffy    schedule 05.05.2014
comment
Вероятно, вы хотели указать на BashFAQ #5. Поскольку № 1 не говорит о массивах. - person vaab; 05.05.2014
comment
@vaab, № 1 говорит напрямую о чтении ввода с разделителями NUL. Найдите пример, описывающий правильное использование с find -print0. - person Charles Duffy; 05.05.2014
comment
В № 1 есть только одно упоминание слова «массив», и это говорит о том, что нужно перейти к № 5. Я чувствую, что № 5 отвечает на мои опасения, а не № 1. Я точно знаю, как читать содержимое, разделенное NUL, с помощью read, как показано в самом вопросе. - person vaab; 05.05.2014
comment
@vaab, он не говорит «массив», но говорит о тексте с разделителями NUL. Найдите -print0. - person Charles Duffy; 05.05.2014


Если кому интересно, вот функция (использующая while), которую я использую для хранения значений из stdin, разделенных NUL:

read_array () {
    local i
    var="$1"
    i=0
    while read -r -d '' value; do
        printf -v "$var[$i]" "%s" "$value"
        i=$[$i + 1]
    done
}

Затем его можно использовать довольно чисто:

$ read_array arr < <(echo -en "A B\0C\nD\0E\0")
$ declare -p arr
declare -a arr='([0]="A B" [1]="C
D" [2]="E")'
person vaab    schedule 05.05.2014
comment
форма арифметического расширения в квадратных скобках устарела. Вы можете полностью удалить эту строку и увеличить i в предыдущей строке: "$var[i++]". - person Robin A. Meade; 15.01.2017

Вот упрощение функции @vaab. Он использует функцию nameref из bash 4.3:

read_array () {
  local -n a=$1
  while read -r -d '' value; do
    a+=("$value")
  done
}

Контрольная работа:

test_it () {
  local -a arr
  read_array arr < <(echo -en "A B\0C\nD\0E\0")
  declare -p arr
}
test_it
person Robin A. Meade    schedule 15.01.2017