2011-08-25 15 views
10

Załóżmy, że mam dwie listy ciągów (lista A i lista B) z dokładnie taką samą liczbą wpisów, N, na każdej liście i chcę zastąpić wszystkie wystąpienia n-tego elementu A n-tym elementem B w pliku w systemie Unix (najlepiej przy użyciu skryptów Bash).Jaki jest skuteczny sposób zamiany listy łańcuchów na inną listę w pliku Unix?

Jaki jest najbardziej wydajny sposób na zrobienie tego?

Nieefektywnym sposobem byłoby wykonanie N połączeń z "sed s/stringA/stringB/g".

Odpowiedz

9

To zrobi to za jednym razem. Odczytuje on listA i listB na tablice awk, a następnie dla każdego wiersza linput analizuje każde słowo i jeśli słowo znajduje się na liście A, słowo jest zastępowane odpowiednim słowem na liścieB.

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 

Jestem zakładając struny w Lista nie zawiera spacje (domyślny separator pola awk)

+1

Fajnie, ogólnie rzecz biorąc, ale z potencjalnym problemem. To rozwiązanie niekoniecznie zachowuje odstęp między słowami na liniach, na których wprowadzane są zmiany; przebiegi białych spacji zostają zmienione na pojedyncze spacje. Ponieważ nie znamy charakteru tekstu, może to nie stanowić problemu i może nawet okazać się zaletą. W każdym razie +1 ode mnie. –

+0

Jest to zdecydowanie bardziej wydajne niż rozwiązanie poniżej, które zapisuje skrypt sed. Ukończono w 3 minuty, co zajęło 3 dni, używając roztworu sed. Zastępuje również całe słowa, mimo że nie jest to pytanie. –

+0

To rozwiązuje cel, ale jak zachować białe przestrzenie? Wygląda na to, że skrypt awk zastępuje je tylko jednym spacji. – Guru

6

Zadzwoń pod numer sed, który napisze skrypt sed, a drugi go użyje? Jeśli twoje listy są w plikach listA i listB, a następnie:

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

Robię kilka przepiękne założenia o „słowach” nie zawierające ani okrężnicy lub symboli procent, ale można dostosować wokół tego. Niektóre wersje sed mają górne ograniczenia liczby poleceń, które można określić; jeśli jest to problem, ponieważ twoje listy słów są wystarczająco duże, być może będziesz musiał podzielić wygenerowany skrypt sed na oddzielne pliki, które zostaną zastosowane - lub zmienić, aby użyć czegoś bez limitu (na przykład Perl).

Kolejnym przedmiotem, o którym należy pamiętać, jest kolejność zmian. Jeśli chcesz zamienić dwa słowa, musisz ułożyć starannie listy słów. Ogólnie rzecz biorąc, jeśli zamapujesz (1) słowoA na słowoB i (2) słowoB na słowoC, ważne jest czy skrypt sed odwzorowuje (1) przed lub po mapowaniu (2).

Pokazany skrypt nie zwraca uwagi na granice słów; możesz zrobić to ostrożnie na różne sposoby, w zależności od wersji sed, której używasz, i twoich kryteriów, co stanowi słowo.

+0

Istnieje również potencjalny problem, że jedno słowo w B jest całkowicie lub częściowo w A. Prawidłowe rozwiązanie prawdopodobnie wymagałoby podzielenia danych wejściowych na słowa i zmiany ich raz, jeśli w ogóle. – lhf

+0

powoduje to błąd: $ paste -d: listA listB | sed 's/\ ([^:] * \): \ ([^:] * \)/s% \ 1% \ 2%'> sed.skrypt sed: -e expression # 1, char 30: unterminated 's 'command – user248237dfsf

+0

@user, naprawiono. –

1

Jest to dość proste z 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

+1 do używania Tcl! –

1

Można to zrobić w bash. Umieść swoje listy w tablicach.

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

Zastosowanie tr (1) (przetłumaczyć lub usunąć znaki):

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

chce zastąpić całe ciągi, a nie pojedyncze znaki –

2

muszę zrobić coś podobnego, a ja likwidacji generowania sed poleceń na podstawie pliku mapy:

$ 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 

Upewnij się, że Twoja powłoka obsługuje tak wiele parametrów, jak na mapie.

+0

piękna jednolinijka !! – once

+0

Czysta wersja 'sed' i' bash': 'sed -f <(sed 's/=> //; s # #/#; s # $ #/#; s #^# s/# 'file.map) stuff.txt'. – agc

Powiązane problemy