Как избежать экранированных символов-разделителей в командах Unix?

Как избежать экранированных символов-разделителей в командах Unix?

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

Пример

1~CEO~ashok\~kumar~1000

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

$ cat test.out|awk -F'~' 'BEGIN {sum=0} {sum+=$4} END{print sum}'

Предположим, что test.outданные следующие:

1~CEO~ashok\~kumar~1000
2~CFO~Ranjan~2000
3~CEO~kumar~1000

Таким образом, мой вывод должен быть 4000. Но в настоящее время с моей командой я получаю только 3000!

решение1

Просто измените экранированный разделитель на что-то другое перед обработкой с помощью awk. Это можно сделать с помощью sed:

$ cat test.out| sed 's/\\~/=/g' | \
    awk -F'~' 'BEGIN {sum=0} {sum+=$4} END{print sum}'
4000

И, как это часто бывает, это catне нужно:

$ sed 's/\\~/=/g' test.out | awk -F'~' 'BEGIN {sum=0} {sum+=$4} END{print sum}'

решение2

Вот альтернатива, которая не использует awk:

$ sed 's/\\~/=/g' test.out | cut -d"~" -f4 | paste -sd+ | bc
4000

Вышеуказанное используется sedдля замены экранированной тильды \~в 3-м столбце. После этого мы можем использовать cutдля выбора 4-го столбца чисел, а затем реконструировать их так, чтобы они были разделены знаками плюс, ( +).

$ sed 's/\\~/=/g' test.out | cut -d"~" -f4 | paste -sd+
1000+2000+1000

Затем эта строка передается двоичному калькулятору, bcкоторый их суммирует.

решение3

Чтобы справиться с экранированием, общий метод заключается в использовании perlили PCRE и их оператора чередования regexp в сочетании с оператором no-backtrack. Здесь с GNU grep:

grep -Po '(?>(?:\\.|.)*?~){3}\K(?:\\.|[^~])*' << \EOF
1~CEO~ashok\~kumar~1000
2~CFO~Ranjan~2000
3~CEO~kumar~1000
4~field2~field3\\~10000~field5-note-the-escaped-backslash-not-tilde
5~a\~b\~c\~no-4th-field-here
EOF

который дает:

1000
2000
1000
10000

(которые вы можете суммировать с вашим обычным awk '{s+=$0};END{print s}').

С помощью GNU sedвы также можете сделать это с помощью:

sed -rn 's/((\\.|[^\~])*~){3}((\\.|[^~])*).*/\3/p'

С помощью GNU awkвы можете FPATопределить поля как последовательности экранированных символов или символов, отличных от тильды и обратной косой черты:

awk -v FPAT='(\\\\.|[^\\\\~])*' '{print $4}'

решение4

Это немного неуклюже в awk (если только вы не можете предварительно обработать свой исходный код, чтобы изменить разделитель, но для этого требуется знать другой символ или последовательность символов, которые не могут встречаться во входных данных). Одна вещь, которую вы можете сделать, это прочитать целую строку, а затем обработать ее, чтобы получить символы новой строки в качестве разделителей (символы новой строки — это единственное, что не может встречаться в строке).

awk 'BEGIN {FS="\n"}
{
    gsub("~", "\n");
    gsub("\\\n", "~");
    gsub("\\\\", "\\");
    $0 = $0;
    print $4;
}'

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