Как эффективный способ заменить список строк другим списком в файле Unix? - PullRequest
10 голосов
/ 26 августа 2011

Предположим, у меня есть два списка строк (список A и список B) с одинаковым количеством записей N в каждом списке, и я хочу заменить все вхождения n-го элемента A на n-й элемент B в файле в Unix (в идеале с использованием сценариев Bash).

Какой самый эффективный способ сделать это?

Неэффективным способом было бы сделать N звонков на "sed s/stringA/stringB/g".

Ответы [ 6 ]

9 голосов
/ 26 августа 2011

Это будет сделано за один проход.Он считывает listA и listB в массивы awk, затем для каждой строки linput проверяет каждое слово, и если слово найдено в listA, слово заменяется соответствующим словом в listB.

awk '
    FILENAME == ARGV[1] { listA[$1] = FNR; next }
    FILENAME == ARGV[2] { listB[FNR] = $1; next }
    {
        for (i = 1; i <= NF; i++) {
            if ($i in listA) {
                $i = listB[listA[$i]]
            }
        }
        print
    }
' listA listB filename > filename.new
mv filename.new filename

Я предполагаю, что строки в listA не содержат пробелов (разделитель полей по умолчанию в awk)

6 голосов
/ 26 августа 2011

Сделать один вызов sed, который пишет сценарий sed, а другой использовать его? Если ваши списки находятся в файлах listA и listB, то:

paste -d : listA listB | sed 's/\([^:]*\):\([^:]*\)/s%\1%\2%/' > sed.script
sed -f sed.script files.to.be.mapped.*

Я делаю несколько широких предположений о том, что «слова» не содержат ни двоеточия, ни символов процента, но вы можете адаптироваться к этому. Некоторые версии sed имеют верхние границы количества команд, которые могут быть указаны; если это проблема из-за того, что ваши списки слов достаточно велики, то вам, возможно, придется разделить сгенерированный скрипт sed на отдельные файлы, которые будут применены, или перейти на использование чего-либо без ограничений (например, Perl).

Еще один момент, о котором следует помнить, это последовательность изменений. Если вы хотите поменять местами два слова, вам нужно тщательно составить свой список слов. В общем, если вы отображаете (1) wordA в wordB и (2) wordB в wordC, имеет значение, выполняет ли скрипт sed отображение (1) до или после преобразования (2).

Показанный скрипт не заботится о границах слов; вы можете быть осторожны с ними различными способами, в зависимости от используемой вами версии sed и ваших критериев того, что составляет слово.

2 голосов
/ 05 декабря 2012

Мне нужно было сделать что-то похожее, и я начал генерировать команды sed на основе файла карты:

$ cat file.map
abc => 123
def => 456
ghi => 789

$ cat stuff.txt
abc jdy kdt
kdb def gbk
qng pbf ghi
non non non
try one abc

$ sed `cat file.map | awk '{print "-e s/"$1"/"$3"/"}'`<<<"`cat stuff.txt`"
123 jdy kdt
kdb 456 gbk
qng pbf 789
non non non
try one 123

Убедитесь, что ваша оболочка поддерживает столько параметров sed, сколько у вас есть на карте.

1 голос
/ 26 августа 2011

вы можете сделать это в bash.Получите ваши списки в массивы.

listA=(a b c)
listB=(d e f)
data=$(<file)
echo "${data//${listA[2]}/${listB[2]}}" #change the 3rd element. Redirect to file where necessary
1 голос
/ 26 августа 2011

Это довольно просто с Tcl:

set fA [open listA r]
set fB [open listB r]
set fin [open input.file r]
set fout [open output.file w]

# read listA and listB and create the mapping of corresponding lines
while {[gets $fA strA] != -1} {
    set strB [gets $fB]
    lappend map $strA $strB
}

# apply the mapping to the input file
puts $fout [string map $map [read $fin]]

# if the file is large, do it line by line instead
#while {[gets $fin line] != -1} {
#    puts $fout [string map $map $line]
#}

close $fA
close $fB
close $fin
close $fout

file rename output.file input.file
0 голосов
/ 26 августа 2011

Используйте tr (1) (перевести или удалить символы):

 cat file | tr 'abc' 'XYZ' > file_new
 mv file_new file
...