Jak zgłosić zmiany w miejscu

23

Czy podczas sedzastępowania ciągów w miejscu istnieje sposób, aby zgłosić dokonane zmiany (bez polegania na różnicach między starymi i nowymi plikami)?

Na przykład, jak mogę zmienić wiersz poleceń

find . -type f | xargs sed -i 's/abc/def/g'

więc mogę zobaczyć zmiany wprowadzane na bieżąco?

ricab
źródło

Odpowiedzi:

15

Można użyć sed„s wflagę albo z /dev/stderr, /dev/tty, /dev/fd/2jeśli jest obsługiwane w systemie. Np. Z wejściem filetakim jak:

foo first
second: missing
third: foo
none here

bieganie

sed -i '/foo/{
s//bar/g
w /dev/stdout
}' file

wyjścia:

bar first
third: bar

chociaż filetreść została zmieniona na:

bar first
second: missing
third: bar
none here

W twoim przypadku uruchamianie:

find . -type f -printf '\n%p:\n' -exec sed -i '/foo/{
s//bar/g
w /dev/fd/2
}' {} \;

edytuje pliki w miejscu i wyświetla:

./file1:
rzeczy w barze
więcej paska

./file2:

./file3:
pierwszy pasek
po trzecie: bar

Możesz również wydrukować coś takiego jak original line >>> modified linenp .:

find . -type f -printf '\n%p:\n' -exec sed -i '/foo/{
h
s//bar/g
H
x
s/\n/ >>> /
w /dev/fd/2
x
}' {} \;

edytuje pliki w miejscu i wyświetla:

./file1:
foo rzeczy >>> rzeczy bar
więcej foo >>> więcej paska

./file2:

./file3:
foo najpierw >>> najpierw pasek
trzeci: foo >>> trzeci: bar
don_crissti
źródło
16

Możesz to zrobić w dwóch przejściach, używając pakcji rint w pierwszym przejściu, używając:

find . -type f | xargs sed --quiet 's/abc/def/gp'

gdzie --quietsprawia, że ​​sed nie pokazuje każdej linii, a psufiks pokazuje tylko linie, w których pasowało podstawienie.

Ma to takie ograniczenie, że sed nie pokaże, które pliki są zmieniane, co oczywiście można naprawić z pewną dodatkową złożonością.

msw
źródło
Nie robi dokładnie tego, co chciałem, ponieważ potrzebujesz dwóch przepustek, ale głosuję, ponieważ właśnie się tego nauczyłem i jest to bardzo przydatne. Zwłaszcza jeśli, jak powiedziałeś, pliki również zostały wydrukowane. Coś w stylufor x in `find . -type f`; do echo ///File $x: ; sed --quiet 's/abc/def/gp' $x; done
ricab,
@msw Mam wiele sedwyrażeń, nie chcę pisać /gpdla każdego z nich. Jak ustawić globalnie?
wszystkie
8

Nie sądzę, żeby to było możliwe, ale obejściem tego problemu może być użycie perla:

find . -type f | xargs perl -i -ne 's/abc/def/ && print STDERR' 

Spowoduje to wydrukowanie zmienionych linii na błąd standardowy. Na przykład:

$ cat foo
fooabcbar
$ find . -type f | xargs perl -i -ne 's/abc/def/ && print STDERR' 
foodefbar

Możesz również uczynić to nieco bardziej złożonym, drukując numer linii, nazwę pliku, oryginalną linię i zmienioną linię:

$ find . -type f | 
   xargs perl -i -ne '$was=$_; chomp($was);
                      s/abc/def/ && print STDERR "$ARGV($.): $was : $_"' 
./foo(1): fooabcbar : foodefbar
terdon
źródło
+1 Możesz także użyć $ARGVdla nazwy obsługiwanego pliku.
Joseph R.
@JosephR. dobra uwaga, dodano.
terdon
Dzięki, to chyba pomocne (sam tego nie testowałem). Nie wybieram, ponieważ nie używa sed, więc nie odpowiada dokładnie na pytanie.
ricab
@ricab w porządku, nie powinieneś akceptować odpowiedzi, chyba że faktycznie odpowiada na twoje pytanie. Pamiętaj jednak, że w przypadku takich prostych rzeczy perlskładnia jest bardzo podobna do tej sedi nie sądzę, że to, o co prosisz, jest w rzeczywistości możliwe sed.
terdon
3

Jest to możliwe przy użyciu wagowo flagę, która zapisuje aktualny wzór do pliku. Tak więc, dodając go do polecenia substitute, możemy raportować kolejne podstawienia do pliku i drukować go po zakończeniu zadania. Lubię też pokolorować zastąpiony ciąg grep.

sed -i -e "s/From/To/gw /tmp/sed.done" file_name
grep --color -e "To" /tmp/sed.done

Zauważ, że musi być tylko jedna spacja między w a nazwą pliku.

Jest to nawet lepsze niż diff, ponieważ diffing może również pokazywać zmiany, nawet jeśli nie zostały wykonane przez sed.

Jacek
źródło
Właśnie go przetestowałem i sed zapisuje tylko linie, które zastąpił sed.done. W związku z tym wiersze z „Do” w oryginalnym pliku nie są drukowane sed.done, więc gdy zobaczysz grep "To" sed.donetylko linie, które zostały zmienione sed. Nie zobaczysz oryginalnej linii w pliku, zanim nie zostanie ona zastąpiona, jeśli to jest to, do czego
zmierzasz
1

Lubię rozwiązanie @terdon - perl jest do tego dobry.

Oto moja ulepszona wersja, która:

  • nie będzie próbował zmieniać plików, które nie mają pasującego łańcucha
  • utworzy kopię zapasową wcześniejszej wersji zmieniających się plików (utwórz wersję .bak)
  • wyświetli listę wszystkich zmienionych plików / lineno i pokaże WSTĘPNE i NOWE wersje tej linii wcięte i pod spodem dla łatwego czytania

kod

find /tmp/test -type f ! -name "*.bak" -exec grep -l '/opt/gridmon' {} \; | xargs -L1 perl -ni'.bak' -e'$old=$_; s/\/opt\/gridmon/~/g && print STDERR "$ARGV($.):\n\tOLD:$old\tNEW:$_"'

przykładowe wyjście

/tmp/test/test4.cfg(13):
    OLD:    ENVFILE /opt/gridmon/server/etc/gridmonserver.cfg
    NEW:    ENVFILE ~/server/etc/gridmonserver.cfg
/tmp/test/test4.cfg(24):
    OLD:    ENVFILE /opt/gridmon/server/etc/gridmonserver.cfg
    NEW:    ENVFILE ~/server/etc/gridmonserver.cfg
andy987
źródło