Что такое эффективный способ заменить список строк другим списком в файле Unix?

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

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

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

Ответ 1

Это сделает это за один проход. Он считывает listA и listB в awk-массивы, затем для каждой строки linput проверяет каждое слово, и если слово найдено в списке A, слово заменяется соответствующим словом в 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)

Ответ 2

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

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

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

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

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

Ответ 3

Мне нужно было сделать что-то подобное, и я закончил генерировать команды 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

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

Ответ 4

Это довольно просто с 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

Ответ 5

вы можете сделать это в 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

Ответ 6

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

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