Linia poleceń: wyszukaj i zamień we wszystkich nazwach plików dopasowanych przez grep

80

Próbuję wyszukać i zamienić ciąg we wszystkich plikach dopasowanych przez grep:

grep -n 'foo' * da mi wynik w postaci:

[filename]:[line number]:[text]

Dla każdego pliku zwróconego przez grep chciałbym zmodyfikować plik, zastępując foogo bar.

Michael Kristofik
źródło

Odpowiedzi:

70

Czy masz na myśli wyszukiwanie i zamianę ciągu we wszystkich plikach dopasowanych przez grep?

perl -p -i -e 's/oldstring/newstring/g' `grep -ril searchpattern *`

Edytować

Ponieważ wydaje się to dość popularne pytanie, pomyślałem, że zaktualizuję.

Obecnie najczęściej używam, ack-grepponieważ jest bardziej przyjazny dla użytkownika. Więc powyższe polecenie brzmiałoby:

perl -p -i -e 's/old/new/g' `ack -l searchpattern`

Aby obsłużyć białe znaki w nazwach plików, możesz uruchomić:

ack --print0 -l searchpattern | xargs -0 perl -p -i -e 's/old/new/g'

możesz zrobić więcej ack-grep. Załóżmy, że chcesz ograniczyć wyszukiwanie tylko do plików HTML:

ack --print0 --html -l searchpattern | xargs -0 perl -p -i -e 's/old/new/g'

A jeśli biała przestrzeń nie jest problemem, jest jeszcze krótsza:

perl -p -i -e 's/old/new/g' `ack -l --html searchpattern`
perl -p -i -e 's/old/new/g' `ack -f --html` # will match all html files
armandino
źródło
3
Myślę, że może to mieć problem z plikami / folderami zawierającymi spacje. Can't open Untitled: No such file or directory, <> line 5podczas próby „Untitled Folder / file.txt”.
Xeoncross
108

Wygląda na to, że chcesz, na podstawie podanego przykładu:

sed -i 's/foo/bar/g' *

Nie jest rekursywny (nie schodzi do podkatalogów). Aby uzyskać fajne rozwiązanie zastępując wybrane pliki w całym drzewie, użyłbym find:

find . -name '*.html' -print -exec sed -i.bak 's/foo/bar/g' {} \;

Jest *.htmlto wyrażenie, które musi pasować do plików, a .bakpo wykonaniu -ikopii oryginalnego pliku z rozszerzeniem .bak (może to być dowolne rozszerzenie), a gna końcu wyrażenia sed nakazuje sedowi zamianę wielu kopii jedna linia (a nie tylko pierwsza). Funkcja -printznajdowania to wygoda pokazania, które pliki zostały dopasowane. Wszystko to zależy od dokładnych wersji tych narzędzi w twoim systemie.

MattJ
źródło
1
Słowo ostrzeżenia dla użytkowników cygwin. combo find i sed wydaje się zmieniać prawa użytkownika do plików, które są przesyłane strumieniowo. Można to po prostu naprawić za pomocą polecenia chmod -R 644 * z tego samego poziomu katalogu, który był używany, gdy operowano find / sed.
kaskelotti
Słowo ostrzeżenia dla ludzi, którzy nie chcą używać argumentu -i: jeśli go nie używasz, to nie działa (nie pytaj mnie dlaczego)
knocte
4
@knocte -i mówi sedowi, aby zmodyfikował plik, w przeciwnym razie po prostu wypisuje zmodyfikowaną wersję na standardowe wyjście. Jeśli nie chcesz tworzyć pliku .bak, po prostu pomiń część „.bak”, -i działa również samodzielnie.
MattJ,
2
W OSX musisz podać findkomendę katalog, od którego ma zacząć, na przykład find . -name '*.html'lub find directoryname/ -name '*'.
Michiel Kauw-A-Tjoe
sed -ie 's/foo/bar/g' *
Musiałem
14

Jeśli urządzenie sed(1)posiada -iopcję, a następnie używać go w ten sposób:

for i in *; do
  sed -i 's/foo/bar/' $i
done

Jeśli nie, istnieje kilka sposobów na różne warianty następujących elementów, w zależności od tego, w jakim języku chcesz grać:

ruby -i.bak -pe 'sub(%r{foo}, 'bar')' *
perl -pi.bak -e 's/foo/bar/' *
Keltia
źródło
2
for i in *; do ...jest zbędne, sed może przyjąć listę plików jako argumenty.
Jens
2
@Jens może ulepszyć tę odpowiedź, dodając własny przykład do powyższego?
Sroka
Zmienne zawsze należy cytowaćfor i in *; do; sed -i 's/foo/bar/g' "$i"; done
kot
6

Podobało mi się i korzystałem z powyższego rozwiązania lub przeszukiwania całego systemu i zastępowania wśród tysięcy plików:

find -name '*.htm?' -print -exec sed -i.bak 's/foo/bar/g' {} \;

Zakładam z „* .htm?” zamiast .html wyszukuje i znajduje zarówno pliki .htm, jak i .html.

Zastąpiłem .bak bardziej używaną w systemie tyldą (~), aby ułatwić czyszczenie plików kopii zapasowych.

hans
źródło
4

Działa to przy użyciu grepa bez konieczności używania perla lub find.

grep -rli 'old-word' * | xargs -i@ sed -i 's/old-word/new-word/g' @
pymarco
źródło
xargs nie ma -i w OSX lub BSD openbsd.org/cgi-bin/man.cgi/OpenBSD-current/man1/… czy chodziło Ci o użycie wielkiej litery „I”?
Tony Adams
Nie wiedziałem, -iże nie działa dla innych systemów operacyjnych. Działa dla mnie na ubuntu.
pymarco
Na mojej stronie xargspodręcznika (w systemie Ubuntu) jest napisane, że -ijest to przestarzałe i do użycia -Izamiast tego. Tak więc powinniśmy powiedzieć:grep -rli 'old-word' * | xargs -I filepath sed -i 's/old-word/new-word/g' filepath
Max Wallace,
3

find . -type f -print0 | xargs -0 <sed/perl/ruby cmd>będzie przetwarzać nazwy plików w wielu przestrzeniach jednocześnie, ładując po jednym tłumaczu na wsad. O wiele szybciej.

koolb
źródło
@knocte, „cmd” jest tokenem dla całego polecenia wyszukiwania i zamiany dla dowolnego wybranego narzędzia. Ta odpowiedź odpowiada na pytanie, jak radzić sobie z nazwami plików zawierającymi białe znaki.
Tony Adams
1

Odpowiedź już udzielona na temat używania funkcji find i sed

find -name '*.html' -print -exec sed -i.bak 's/foo/bar/g' {} \;

to prawdopodobnie standardowa odpowiedź. Lub możesz użyć perl -pi -e s/foo/bar/g'zamiast sedpolecenia.

W przypadku większości szybkich zastosowań może się okazać, że polecenie rpl jest łatwiejsze do zapamiętania. Oto zamiana (foo -> bar), rekurencyjnie dla wszystkich plików w bieżącym katalogu:

rpl -R foo bar .

Nie jest domyślnie dostępny w większości dystrybucji Linuksa, ale można go szybko zainstalować ( apt-get install rpllub podobnie).

Jednak w przypadku trudniejszych zadań, które obejmują wyrażenia regularne i podstawianie z powrotem lub zmianę nazw plików, a także wyszukiwanie i zastępowanie, najbardziej ogólnym i potężnym narzędziem, o którym wiem, jest repren , mały skrypt w Pythonie, który napisałem jakiś czas temu bardziej skomplikowane zadania związane ze zmianą nazw i refaktoryzacją. Powody, dla których możesz to preferować, to:

  • Obsługa zmiany nazw plików, a także wyszukiwania i zastępowania zawartości plików (w tym przenoszenia plików między katalogami i tworzenia nowych katalogów nadrzędnych).
  • Zobacz zmiany, zanim zdecydujesz się przeprowadzić wyszukiwanie i zamianę.
  • Obsługuje wyrażenia regularne z podstawianiem wstecz, całymi słowami, bez rozróżniania wielkości liter i zachowaniem wielkości liter (zamień foo -> bar, Foo -> Bar, FOO -> BAR).
  • Działa z wieloma zamiennikami, w tym swapami (foo -> bar i bar -> foo) lub zestawami nieunikalnych zamienników (foo -> bar, f -> x).

Przykłady można znaleźć w pliku README .

jlevy
źródło
1

W rzeczywistości jest to łatwiejsze niż się wydaje.

grep -Rl 'foo' ./ | xargs -n 1 -I % sh -c "ls %; sed -i 's/foo/bar/g' %";
  • grep powtarza się w twoim drzewie (-R) i wypisuje tylko nazwę pliku (-l), zaczynając od bieżącego katalogu (./)
  • który jest przesyłany potokiem do xargs, który przetwarza je pojedynczo (-n 1) i używa% jako symbolu zastępczego (-I%) w poleceniu powłoki (sh -c)
  • w poleceniu powłoki najpierw wypisywana jest nazwa pliku (ls%;)
  • następnie sed wykonuje operację inline (-i), podstawienie ('s /') foo przez bar (foo / bar), globalnie (/ g) na pliku (ponownie, reprezentowane przez%)

Bułka z masłem. Jeśli dobrze zrozumiesz find, grep, xargs, sed i awk, prawie nic nie jest niemożliwe, jeśli chodzi o manipulowanie plikami tekstowymi w bash :)

siliconrockstar
źródło
Ach, lekceważ, ten pymarco już to omówił powyżej. Pozostawiając odpowiedź na wyjaśnienie.
siliconrockstar