Как заставить ksh читать нулевые поля
У меня есть файл с разделителями табуляции с некоторыми полями, потенциально не содержащие данных. В ksh 'read' рассматривает несколько вкладок как один разделитель. Есть ли способ изменить это поведение, чтобы я тоже мог иметь пустые данные? Т.е. при встрече с 2-мя вкладками он будет восприниматься как нулевое поле? Или я должен использовать awk?
# where <TAB> would be a real tab:
while IFS="<TAB>" read a b c d; do echo $c; done < file.txt
ср
awk -F"\t" '{print $3}' file.txt
Версия оболочки выведет неправильное поле, если 1-я или 2-я запись пуста.
2 ответа
Действительно, можно использовать современную оболочку Korn непосредственно для обработки каждого символа табуляции как разделителя столбцов, так что несколько последовательных вкладок будут разделять пустые поля без sed, awk или perl. Хитрость заключается в том, чтобы установить для переменной IFS 2 последовательных символа табуляции, например:
IFS=$'\t\t'
Цикл while в следующем коде будет читать файл с разделенными табуляцией значениями, помещая поля каждой строки в простой индексированный массив. Внутренний цикл for просто выводит прочитанное, по одному полю на строку вывода:
typeset -a Cols
while IFS=$'\t\t' read -A Cols
do
for (( i=0 ; i < ${#Cols[@]} ; i++ ))
do
print "Cols[$i] '${Cols[$i]}' "
done
done
И да, это также будет правильно обрабатывать строку, начинающуюся с символа табуляции, как имеющую нулевое значение для столбца 1, то есть в приведенном выше Cols[0] будет установлено значение null.
Я протестировал это на /bin/ksh 'AJM93u+ 2012-08-01' на macOS High Sierra, но он должен работать с версиями ksh с открытым исходным кодом AT&T AST, начиная с 10 лет и более. Смотрите также https://github.com/att/ast
read будет искать первое поле, игнорируя IFS. Еще одна демонстрация этой проблемы
echo " b c d e" | while read a b c d e; do echo c=$c; done
Я буду продолжать использовать пробел в качестве IFS, чуть проще для тестирования.
Избежать awk можно с помощью cut:
echo c=$(echo " b c d e" | cut -d" " -f3)
Когда вы хотите назначить все поля за один прогон, вы застрянете с разрезом.
Sed принимает разные опции -e и работает с ними в указанном порядке. Вы можете получить поля по
eval $(echo " b c d e" |
sed -e 's/^/a=/' -e 's/ /;b=/' -e 's/ /;c=/' -e 's/ /;d=/' -e 's/ /;e=/')
echo check:
set | grep "^[a-e]="
Вы доверяете своему вкладу или предпочитаете awk выше sed?