awk: сначала разбить строку на отдельные строки; во-вторых, используйте эти новые строки в качестве нового ввода - PullRequest
1 голос
/ 03 апреля 2020

Допустим, у меня есть эта строка:

foo|bar|foobar

Я хочу разделить ее на | и , а затем использовать эти 3 новые строки в качестве входных данных для дальнейших процедур (давайте скажем заменить bar на xxx).

Конечно, я могу передать два экземпляра awk, например:

echo "foo|bar|foobar" | awk '{gsub(/\|/, "\n"); print}' | awk '/bar/ {gsub(/bar/, "xxx"); print}'

Но как мне добиться этого в одном скрипте? Сначала выполнить одну операцию для некоторого ввода, а затем обработать результат как новый ввод для второй операции?

Я пробовал что-то вроде этого:

echo "foo|bar|foobar" | awk -v c=0 '{
        {
            gsub(/\|/, "\n");
            sprintf("%s", $0);
        }
        {
            if ($0 ~ /bar/) {
                c+=1;
                gsub(/bar/, "xxx");
                print c;
                print
            }
        }
    }'

Что приводит к такому:

1
foo
xxx
fooxxx

И благодаря счетчику c совершенно очевидно, что последующий if не обрабатывает многострочный ввод, который он получает как несколько новых записей , а вместо этого просто как одна многострочная запись.

Таким образом, мой вопрос таков: как заставить awk обрабатывать эту новую многострочную запись, получающую столько же однострочных записей?

Требуемый вывод в этот пример должен выглядеть примерно так, если я прав:

1
xxx
2
fooxxx

Но это всего лишь пример, вопрос скорее в механике такого перехода.

Ответы [ 3 ]

2 голосов
/ 03 апреля 2020

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

echo "foo|bar|foobar" |\
    awk '{
             count = 0
             n = split($0, arr, "|")
             for ( i = 1; i <= n; i++ )
             {
                 if ( arr[i] ~ /bar/ )
                 {
                     count += sub(/bar/, "xxx", arr[i])
                     print count
                     print arr[i]
                 }
             }
         }'

Также вам не требуется явное приращение переменной count, sub() возвращает количество замен, сделанных в исходной строке. Вы можете просто увеличить существующее значение count.

В качестве еще одного уровня оптимизации вы можете избавиться от совпадения ~ в условии if и напрямую использовать там функцию sub()

if ( sub(/bar/, "xxx", arr[i]) )
{
    count++
    print count
    print arr[i]
}
1 голос
/ 03 апреля 2020

С GNU awk:

$ awk -v RS='[|\n]' 'gsub(/bar/,"xxx"){print ++c ORS $i}' file
1
xxx
2
fooxxx

С любым awk:

$ awk -F'|' '{c=0; for (i=1;i<=NF;i++) if ( gsub(/bar/,"xxx",$i) ) print ++c ORS $i }' file
1
xxx
2
fooxxx
1 голос
/ 03 апреля 2020

Если вы установите разделитель записей (RS) на символ канала, вы почти получите желаемый эффект, например:

echo 'foo|bar|foobar' | awk -v RS='|' 1

Вывод:

foo
bar
foobar
[...an empty line

За исключением этого символ новой строки становится частью последнего поля, поэтому в конце вывода есть дополнительная строка. Вы можете обойти это, либо добавив новую строку в переменную RS, сделав ее менее переносимой, либо избегая отправки новых строк в awk.

Например, используя менее переносимый способ:

echo 'foo|bar|foobar' | awk -v RS='\\||\n' '{ sub(/bar/, "baz") } 1'

Вывод:

foo                                                                    
baz
foobaz

Обратите внимание, что пустая запись в конце игнорируется.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...