Zapisz zmiany na miejscu dzięki NK GNU awk

9

Natknąłem się na pytanie (na samym SO), w którym OP musi dokonać edycji i zapisać operację w samych plikach wejściowych.

Wiem, że dla jednego pliku wejściowego możemy wykonać następujące czynności:

awk '{print "test here..new line for saving.."}' Input_file > temp && mv temp Input_file

Powiedzmy teraz, że musimy wprowadzić zmiany w tym samym formacie plików (załóżmy tutaj .txt).

Co próbowałem / pomyślałem o tym problemie: jego podejście polega na przechodzeniu przez pętlę for plików .txt i wywoływanie singlaawkjest bolesnym i NIE zalecanym procesem, ponieważ marnuje zbędne cykle procesora, a dla większej liczby plików byłoby więcej powolny.

Co więc można zrobić tutaj, aby przeprowadzić edycję w miejscu dla wielu plików z NON GNU, awkktóry nie obsługuje opcji inplace. Przeszedłem również przez ten wątek. Zapisz modyfikacje za pomocą awk, ale nic nie ma na imadło NON GNU awk i zmiana wielu plików w awksobie, ponieważ awk inny niż GNU nie ma inplacetakiej opcji.

UWAGA: Dlaczegobashdodaję znacznik, ponieważ w części z odpowiedziami użyłem poleceń bash, aby zmienić nazwy plików tymczasowych na ich rzeczywiste nazwy pliku_wejściowego, więc dodając go.



EDYCJA: Zgodnie z komentarzem Eda, dodając tutaj przykładowy przykład, cel kodu tego wątku może być również użyty do ogólnej edycji w miejscu.

Przykładowe pliki wejściowe:

cat test1.txt
onetwo three
tets testtest

cat test2.txt
onetwo three
tets testtest

cat test3.txt
onetwo three
tets testtest

Próbka oczekiwanej wydajności:

cat test1.txt
1
2

cat test2.txt
1
2

cat test3.txt
1
2
RavinderSingh13
źródło
1
Interesujący i istotny problem z awk ++
anubhava
1
@ RavinderSingh13, jeśli masz całą masę plików do zastosowania, dlaczego nie użyć pojedynczego wywołania awk(być może w podpowłoce) lub {...}zamkniętej grupy, a następnie zapisać wyniki w pożądanym pliku wyjściowym (dla każdego pliku wejściowego, lub połączony plik dla wszystkich plików wejściowych). Następnie przekierowujesz dane wyjściowe grupy otoczki lub grupy nawiasów klamrowych do bieżącego pliku, w którym jest zapisywany? Zwykłe dołączenie ciągu plików wejściowych po awkpoleceniu przetworzyłoby kolejno wszystkie pliki (lub coś podobnego)?
David C. Rankin,
@ DavidC.Rankin, dziękuję za odpowiedź w tej sprawie. Tak, zamieściłem podobne rzeczy, o których pan mówi, moja odpowiedź jest również zamieszczona w tym pytaniu. Pozwólcie mi poznać wasze opinie na temat tego samego pana, na zdrowie.
RavinderSingh13,
1
Po pewnym czasie snu i przemyśleniu tego, widzę 2 opcje (1) z awk {..} file1 .. fileXzapisem zmodyfikowanego pliku, ponieważ np. temp01W następnej iteracji podczas przetwarzania następnego pliku użyj a, mv -f tmp01 input01aby zastąpić plik wejściowy zmodyfikowanymi danymi; lub (2) po prostu napisz nowy katalog ./tmp/tmp01 ... ./tmp/tmp0Xpodczas wykonywania awkskryptu i kontynuuj z pętlą nad plikami w ./tmpkatalogu i np. mv -f "$i" "input_${i##*[^0-9]}"(lub jakimkolwiek rozszerzeniem potrzebnym do zastąpienia starych plików wejściowych.
David C. Rankin
@ DavidC.Rankin, Dziękujemy za poinformowanie o tym tutaj, proszę pana, pierwsza opcja IMHO może być nieco ryzykowna, ponieważ robimy coś bez awkpełnego uzupełnienia kodu, druga opcja jest prawie taka sama, jak używam w mojej sugestii, będzie bądź wdzięczny, jeśli mógłbyś przekazać swoje myśli na temat tego rozwiązania.
RavinderSingh13,

Odpowiedzi:

6

Ponieważ głównym celem tego wątku jest to, jak zrobić w miejscu SAVE w NON GNU, awkdlatego publikuję najpierw jego szablon, który pomoże każdemu w dowolnym wymaganiu, należy dodać / dołączyć BEGINi ENDsekcję w kodzie, zachowując główny BLOK zgodnie z ich wymagania i powinien dokonać edycji w miejscu, a następnie:

UWAGA: Poniższy zapisuje wszystkie dane wyjściowe do pliku_wyjściowego, więc jeśli chcesz coś wydrukować na standardowe wyjście, dodaj tylkoprint...instrukcję bez> (out)następującego.

Ogólny szablon:

awk -v out_file="out" '
FNR==1{
close(out)
out=out_file count++
rename=(rename?rename ORS:"") "mv \047" out "\047 \047" FILENAME "\047"
}
{
    .....your main block code.....
}
END{
 if(rename){
   system(rename)
 }
}
' *.txt


Konkretne dostarczone rozwiązanie próbki:

awkWymyśliłem następujące podejście w sobie (dla dodanych próbek poniżej jest moje podejście do rozwiązania tego i zapisania danych wyjściowych w samym pliku_pliku_wejściowego)

awk -v out_file="out" '
FNR==1{
  close(out)
  out=out_file count++
  rename=(rename?rename ORS:"") "mv \047" out "\047 \047" FILENAME "\047"
}
{
  print FNR > (out)
}
END{
  if(rename){
    system(rename)
  }
}
' *.txt

UWAGA: jest to tylko test zapisywania edytowanych danych wyjściowych w samym pliku (plikach) Input_file, można użyć sekcji BEGIN wraz z sekcją END w swoim programie, sekcja główna powinna odpowiadać wymaganiom konkretnego pytania.

Uczciwe ostrzeżenie: Również dlatego, że takie podejście tworzy nowy tymczasowy plik wyjściowy na ścieżce, więc lepiej upewnij się, że mamy wystarczającą ilość miejsca w systemach, chociaż w ostatecznym wyniku zachowa tylko główne pliki wejściowe, ale podczas operacji potrzebuje miejsca w katalogu system /



Poniżej znajduje się test powyższego kodu.

Wykonanie programu na przykładzie: Załóżmy, że są następujące.txtpliki wejściowe:

cat << EOF > test1.txt
onetwo three
tets testtest
EOF

cat << EOF > test2.txt
onetwo three
tets testtest
EOF

cat << EOF > test3.txt
onetwo three
tets testtest
EOF

Teraz, gdy uruchamiamy następujący kod:

awk -v out_file="out" '
FNR==1{
  close(out)
  out=out_file count++
  rename=(rename?rename ORS:"") "mv \047" out "\047 \047" FILENAME "\047"
}
{
  print "new_lines_here...." > (out)
}
END{
  if(rename){
    system("ls -lhtr;" rename)
  }
}
' *.txt

UWAGA:ls -lhtr Wsystemsekcji celowomam miejsce,aby zobaczyć, które pliki wyjściowe tworzy (tymczasowo), ponieważ później zmieni ich nazwy na swoje rzeczywiste nazwy.

-rw-r--r-- 1 runner runner  27 Dec  9 05:33 test2.txt
-rw-r--r-- 1 runner runner  27 Dec  9 05:33 test1.txt
-rw-r--r-- 1 runner runner  27 Dec  9 05:33 test3.txt
-rw-r--r-- 1 runner runner  38 Dec  9 05:33 out2
-rw-r--r-- 1 runner runner  38 Dec  9 05:33 out1
-rw-r--r-- 1 runner runner  38 Dec  9 05:33 out0

Kiedy wykonamy skrypt ls -lhtrpo awkuruchomieniu, możemy zobaczyć tylko .txtpliki.

-rw-r--r-- 1 runner runner  27 Dec  9 05:33 test2.txt
-rw-r--r-- 1 runner runner  27 Dec  9 05:33 test1.txt
-rw-r--r-- 1 runner runner  27 Dec  9 05:33 test3.txt


Objaśnienie: Dodanie tutaj szczegółowego wyjaśnienia powyższego polecenia:

awk -v out_file="out" '                                    ##Starting awk program from here, creating a variable named out_file whose value SHOULD BE a name of files which are NOT present in our current directory. Basically by this name temporary files will be created which will be later renamed to actual files.
FNR==1{                                                    ##Checking condition if this is very first line of current Input_file then do following.
  close(out)                                               ##Using close function of awk here, because we are putting output to temp files and then renaming them so making sure that we shouldn't get too many files opened error by CLOSING it.
  out=out_file count++                                     ##Creating out variable here, whose value is value of variable out_file(defined in awk -v section) then variable count whose value will be keep increment with 1 whenever cursor comes here.
  rename=(rename?rename ORS:"") "mv \047" out "\047 \047" FILENAME "\047"     ##Creating a variable named rename, whose work is to execute commands(rename ones) once we are done with processing all the Input_file(s), this will be executed in END section.
}                                                          ##Closing BLOCK for FNR==1  condition here.
{                                                          ##Starting main BLOCK from here.
  print "new_lines_here...." > (out)                       ##Doing printing in this example to out file.
}                                                          ##Closing main BLOCK here.
END{                                                       ##Starting END block for this specific program here.
  if(rename){                                              ##Checking condition if rename variable is NOT NULL then do following.
    system(rename)                                         ##Using system command and placing renme variable inside which will actually execute mv commands to rename files from out01 etc to Input_file etc.
  }
}                                                          ##Closing END block of this program here.
' *.txt                                                    ##Mentioning Input_file(s) with their extensions here.
RavinderSingh13
źródło
1
Ciekawostka: jeśli usuniesz plik wejściowy w FNR==1bloku, nadal możesz zapisać zmiany w miejscu. Jak awk 'FNR==1{system("rm " FILENAME)} {print "new lines" > FILENAME}' files.... Nie jest to wcale wiarygodne (najprawdopodobniej nastąpi całkowita utrata danych), ale nadal działa w większości dobrze: D
oguz ismail
1
Bardzo dobrze wyjaśnione obejście
anubhava,
3

Prawdopodobnie wybrałbym coś takiego, gdybym spróbował to zrobić:

$ cat ../tst.awk
FNR==1 { saveChanges() }
{ print FNR > new }
END { saveChanges() }

function saveChanges(   bak, result, mkBackup, overwriteOrig, rmBackup) {
    if ( new != "" ) {
        bak = old ".bak"
        mkBackup = "cp \047" old "\047 \047" bak "\047; echo \"$?\""
        if ( (mkBackup | getline result) > 0 ) {
            if (result == 0) {
                overwriteOrig = "mv \047" new "\047 \047" old "\047; echo \"$?\""
                if ( (overwriteOrig | getline result) > 0 ) {
                    if (result == 0) {
                        rmBackup = "rm -f \047" bak "\047"
                        system(rmBackup)
                    }
                }
            }
        }
        close(rmBackup)
        close(overwriteOrig)
        close(mkBackup)
    }
    old = FILENAME
    new = FILENAME ".new"
}

$ awk -f ../tst.awk test1.txt test2.txt test3.txt

Wolałbym najpierw skopiować oryginalny plik do kopii zapasowej, a następnie wykonać operację zapisywania zmian w oryginale, ale spowodowałoby to zmianę wartości zmiennej FILENAME dla każdego niepożądanego pliku wejściowego.

Zauważ, że jeśli posiadasz oryginalne pliki o nazwie whatever.baklub whatever.neww katalogu, zastąpisz je plikami tymczasowymi, więc musisz również dodać test. Wywołanie w mktempcelu uzyskania nazw plików tymczasowych byłoby bardziej niezawodne.

FAR bardziej użyteczną rzeczą, jaką można mieć w tej sytuacji, byłoby narzędzie, które wykonuje dowolne inne polecenie i wykonuje część edycji „inplace”, ponieważ można jej użyć do edycji „inplace” dla POSIX sed, awk, grep, tr, cokolwiek i nie wymagałoby zmiany składni skryptu na print > outitp. za każdym razem, gdy chcesz wydrukować wartość. Prosty, delikatny przykład:

$ cat inedit
#!/bin/env bash

for (( pos=$#; pos>1; pos-- )); do
    if [[ -f "${!pos}" ]]; then
        filesStartPos="$pos"
    else
        break
    fi
done

files=()
cmd=()
for (( pos=1; pos<=$#; pos++)); do
    arg="${!pos}"
    if (( pos < filesStartPos )); then
        cmd+=( "$arg" )
    else
        files+=( "$arg" )
    fi
done

tmp=$(mktemp)
trap 'rm -f "$tmp"; exit' 0

for file in "${files[@]}"; do
    "${cmd[@]}" "$file" > "$tmp" && mv -- "$tmp" "$file"
done

którego użyjesz w następujący sposób:

$ awk '{print FNR}' test1.txt test2.txt test3.txt
1
2
1
2
1
2

$ ./inedit awk '{print FNR}' test1.txt test2.txt test3.txt

$ tail test1.txt test2.txt test3.txt
==> test1.txt <==
1
2

==> test2.txt <==
1
2

==> test3.txt <==
1
2

Jednym oczywistym problemem związanym z tym ineditskryptem jest trudność z identyfikacją plików wejściowych / wyjściowych oddzielnie od polecenia, gdy masz wiele plików wejściowych. Powyższy skrypt zakłada, że ​​wszystkie pliki wejściowe pojawiają się jako lista na końcu polecenia, a polecenie jest uruchamiane przeciwko nim pojedynczo, ale oczywiście oznacza to, że nie można go używać do skryptów, które wymagają 2 lub więcej plików na czas, np .:

awk 'NR==FNR{a[$1];next} $1 in a' file1 file2

lub skrypty ustawiające zmienne między plikami na liście arg, np .:

awk '{print $7}' FS=',' file1 FS=':' file2

Uczynienie go bardziej solidnym jako ćwiczenie dla czytelnika, ale spójrz na xargsstreszczenie jako punkt wyjścia do tego, jak solidny ineditmusiałby działać :-).

Ed Morton
źródło
0

Rozwiązanie powłoki jest proste i prawdopodobnie wystarczająco szybkie:

for f in *.txt
do  awk '...' $f > $f.tmp
    mv $f.tmp $f
done

Poszukaj innego rozwiązania tylko wtedy, gdy ostatecznie wykazałeś, że jest to zbyt wolne. Pamiętaj: przedwczesna optymalizacja jest źródłem wszelkiego zła.

użytkownik448810
źródło
Dziękuję za odpowiedź, ale jak wspomniano w moim pytaniu, jesteśmy świadomi tej odpowiedzi, ale jest to naprawdę przesada w wykonywaniu tego zadania, dlatego wspomniałem, czy możemy spróbować czegoś w obrębie samego awk. Dziękujemy za poświęcony czas i odpowiedzcie na zdrowie.
RavinderSingh13,