Применять подстановки строк построчно - PullRequest
0 голосов
/ 16 мая 2019

Мне нужно применить серию подстановок к текстовому файлу, используя файл фильтра с тем же количеством строк: строка n фильтра должна применяться к строке n исходного файла.

например. Оригинальный файл:

foo
bar
foobar

Файл фильтра:

s/oo/uu/
s/a/i/
s/b/l/

Ожидаемый результат:

fuu
bir
foolar

Поскольку sed будет применять каждый фильтр к каждой строке, использование sed -f filterfile особенно неэффективно (число строк довольно велико, поэтому также довольно велико ...). Кроме того, хотя в моем конкретном случае я могу изменить фильтры, чтобы избежать этой проблемы, эта команда приведет к неверным результатам в примере.

В настоящее время я реализую следующий подход (все еще пытаюсь исправить проблему с таблицами…):

paste -d'@' filterA filterB infile \
  |while IFS="@" read AA BB LINE;
do
  echo $LINE|"s/$AA/$BB/g"
done > outfile

Но мне интересно, было ли более элегантное решение, например какой-нибудь sed вариант? (Желательно со стандартными инструментами GNU / Linux.)

Ответы [ 2 ]

3 голосов
/ 16 мая 2019

Вы можете изменить файл фильтра, добавив правильный адрес строки перед каждой строкой

$ nl filter
     1  s/oo/uu/
     2  s/a/i/
     3  s/b/l/

и затем передайте это в sed:

$ nl filter | sed -f- infile
fuu
bir
foolar

Если замены должны быть глобальными, сначала добавьте g:

$ sed 's/$/g/' filter
s/oo/uu/g
s/a/i/g
s/b/l/g

в результате

sed 's/$/g/' filter | nl | sed -f- infile

Небольшая оптимизация для запуска следующего цикла после подстановки заключается в добавлении команды b после нее:

sed 's/.*/{&g;b}/' filter | nl | sed -f- infile

Это немедленно запускает следующий цикл. Эффект для 30,000-строчной версии входных и фильтрующих файлов из вопроса составляет примерно 20% экономии времени:

$ wc -l filter infile
 33033 filter
 33033 infile
 66066 total
$ time sed 's/$/g/' filter | nl | sed -f- infile >/dev/null

real    0m15.868s
user    0m15.522s
sys     0m0.296s
$ time sed 's/.*/{&g;b}/' filter | nl | sed -f- infile >/dev/null

real    0m12.238s
user    0m11.901s
sys     0m0.271s

Если ваш файл большой, awk работает намного быстрее (код любезно предоставлен Ed Morton):

$ time awk 'NR==FNR{o[NR]=$2;n[NR]=$3;next} {gsub(o[FNR],n[FNR])} 1' filter infile >/dev/null

real    0m0.073s
user    0m0.061s
sys     0m0.007s
2 голосов
/ 16 мая 2019
awk -F'/' '
NR==FNR {
    old[NR] = $2
    new[NR] = $3
    next
}
{ gsub(old[FNR],new[FNR]) }
1' filterfile originalfile
fuu
bir
foolar

Вышеописанное будет работать с использованием любого awk в любой оболочке на любом компьютере UNIX.

...