Как найти последнее вхождение строки в столбце 1 и заменить соответствующее значение в столбце 3?

Una soluciónbash(4.0+ )que construye una tabla lookup(una matriz asociativa )utilizando las teclas en mayúsculas -inferiores de VAR1y los valores asociados. Luego revisa los valores en VAR2y construye VAR3con valores de la tabla de búsqueda, o de VAR2si no hay ninguna clave en la tabla de búsqueda correspondiente a la cadena VAR2actual.

VAR1=tom:rich,LIAm:viki
VAR2=liam,albert,tom

declare -A lookup

# build lookup table
while read -d, key_value; do
    # $key_value is a string like "tom:rich", separate these into key and value:
    IFS=: read key value <<<"$key_value"

    # add lower-cased key to table with value
    lookup[${key,,}]=$value
done <<<"$VAR1,"

# do lookups in table
while read -d, string; do
    # get newstring from table, but use $string if there's no entry:
    newstring=${lookup[${string,,}]:-$string}

    # add $newstring to VAR3, with a delimiting comma unless VAR3 is empty
    VAR3+="${VAR3:+,}'$newstring'"
done <<<"$VAR2,"

printf 'VAR3 = %s\n' "$VAR3"

Este código genera

VAR3 = 'viki','albert','rich'

Esto supone que los valores en VAR1y VAR2no contienen líneas nuevas.

2
01.08.2019, 01:45
3 ответа

Чистый раствор sedбез трубопроводов иtac

В таком случае линейный -за -линейный подход из sedне помогает. Лучше обработать весь буфер сразу, так как вариант -zGNU sedделает (вы, кажется, используете linux и GNU sed, переносную замену см. в этом Q&A).

Теперь вы можете воспользоваться жадным характером.*:Шаблон .*apple1будет соответствовать всему, включая последнее вхождение apple1, потому что все остальные вхождения поглощаются .*.

Затем просто добавьте следующие поля(\s+для разделителя столбцов, [0-9]+для второго столбца и еще один \s+, все расширенные регулярные выражения GNU )и окружите их (), чтобы вы могли использовать их повторно. в замене как \1. Затем добавьте третий столбец за пределами (), чтобы заменить его, и получится

sed -zE 's/(.*\napple1\s+[0-9]+\s+)[0-9]+/\14444444/'

Вот именно.

Примечание для пользователей, не -GNU sed:Портативное решение было бы менее удобным:

sed -E 'H;1h;$!d;x;s/(.*\napple1[[:space:]]+[0-9]+[[:space:]]+)[0-9]+/\14444444/'
1
27.01.2020, 22:08
tac file |
awk -v string='apple1' -v replace='444444444' '
    !flag && $1 == string { $3 = replace; flag = 1 }
                          { print }' |
tac

Этот конвейер сначала меняет порядок строк в данных, используя tacиз GNU coreutils. Последняя строка, где 1-й столбец представляет собой определенную строку, легче найти таким образом.

Команда awkпросто сравнивает первый столбец с заданной строкой, и если мы еще не сделали замену(!flagне -ноль ), мы изменяем третий столбец, как только мы найти строку в 1-м столбце. При этом мы также устанавливаем flagв единицу, чтобы не производить дальнейших замен.

Остальная часть программы awkпросто печатает текущую строку (, включая измененную ).

В конце конвейера мы снова меняем порядок строк с помощью tac.

Результатом этого, учитывая данные в вопросе, является

apple1        10109283      20012983
apple1        10983102      10293809
apple1 10293893 444444444
apple10       109283019     109238901
apple10       192879234     234082034
apple10       234908443     3450983490

Столбцы в измененной строке немного отличаются от столбцов в других строках из-за модификации 3-го столбца. Чтобы он выглядел лучше, вы можете пропустить результат через дополнительную стадию column -tв конце конвейера. Если вы это сделаете, вывод будет выглядеть как

apple1   10109283   20012983
apple1   10983102   10293809
apple1   10293893   444444444
apple10  109283019  109238901
apple10  192879234  234082034
apple10  234908443  3450983490

с несколькими пробелами между столбцами.


С sedэто не так просто, как просто заменить 3-й столбец в первой строке, где строка встречается в 1-м столбце (, предполагая, что мы меняем местами строки данных, как в приведенном выше конвейере ). Мы также должны не заменять 3-й столбец в любых последующих строках, даже если 1-й столбец соответствует нашей строке.

Это sedсценарий редактирования, который делает это правильно (может быть любое количество его вариантов, которые могут работать):

/^apple1\>/ ! {
        p
        d
}

s/[[:digit:]]*$/444444444/

:loop
n
$ ! b loop

Первая часть заботится о печати строк в начале ввода, которые не соответствуют apple1в первом столбце.\>в выражении соответствует концу слова apple1, так что мы случайно не найдем apple10или apple12или любую другую подобную строку.p(print )иd(delete + continue со следующей строкой сверху скрипта )внутри {... }выполняются для каждой строки в начале ввода, которая не . ] соответствует выражению.

Команда s(замена )выполняется для первой строки ввода, в которой соответствует apple1в начале строки. Он просто заменяет строку цифр в конце строки на наши 4s.

Затем следует секция с меткой loop, которая заботится о передаче остальных данных без изменений путем печати текущей строки и чтения следующей строки с помощьюn(nвыполняет как печать, так и чтение ). «Текущая строка» будет изменена командой sпри первом проходе по этому циклу.

Самая последняя строка возвращается к метке loop, если мы еще не достигли последней строки ввода.

Пример запуска:

$ tac file | sed -f script.sed | tac
apple1        10109283      20012983
apple1        10983102      10293809
apple1        10293893      444444444
apple10       109283019     109238901
apple10       192879234     234082034
apple10       234908443     3450983490
1
27.01.2020, 22:08

Пробовал использовать команду «Нижний», все работает нормально

for i in `awk '{print $1}' file1| awk '{if(!seen[$1]++)print }'`; do j=`awk -v i="$i" '$1 == i {print $0}' file1| awk '{print NR}'| sed -n '$p'`; awk -v i="$i" '$1 == i {print $0}' file1|awk -v i="$i" -v j="$j" 'NR==j{$3="444444444"}1'; done
0
27.01.2020, 22:08

Теги

Похожие вопросы