Znajdź i zamień wewnątrz pliku tekstowego z polecenia Bash

561

Jaki jest najprostszy sposób znalezienia i zastąpienia danego ciągu wejściowego, powiedzmy abc, i zastąpienia innym ciągiem, powiedzmy XYZw pliku /tmp/file.txt?

Piszę aplikację i używam IronPython do wykonywania poleceń przez SSH - ale nie znam tak dobrze Uniksa i nie wiem, czego szukać.

Słyszałem, że Bash, oprócz tego, że jest interfejsem wiersza poleceń, może być bardzo potężnym językiem skryptowym. Więc jeśli to prawda, zakładam, że możesz wykonywać takie działania.

Czy mogę to zrobić za pomocą bash i jaki jest najprostszy (jednowierszowy) skrypt do osiągnięcia mojego celu?

Popiół
źródło

Odpowiedzi:

927

Najprostszym sposobem jest użycie sed (lub perl):

sed -i -e 's/abc/XYZ/g' /tmp/file.txt

Który wywoła sed, aby dokonać edycji w miejscu ze względu na tę -iopcję. Można to wywołać z bash.

Jeśli naprawdę chcesz użyć po prostu bash, to mogą działać następujące czynności:

while read a; do
    echo ${a//abc/XYZ}
done < /tmp/file.txt > /tmp/file.txt.t
mv /tmp/file.txt{.t,}

Zapętla się nad każdą linią, zastępuje i zapisuje do pliku tymczasowego (nie chcę blokować danych wejściowych). Ruch na końcu przenosi tymczasowo do oryginalnej nazwy.

Jasio
źródło
3
Tyle że wywoływanie mv jest prawie tak samo jak „non Bash” jak używanie sed. Prawie powiedziałem to samo o echu, ale jest to wbudowana powłoka.
szczupły
5
Argument -i dla sed nie istnieje jednak dla Solaris (i pomyślałbym, że istnieją inne implementacje), więc miej to na uwadze. Właśnie spędziłem kilka minut zastanawiając się nad tym ...
Panky,
2
Uwaga do siebie: o wyrażeniu regularnym sed: s/..../..../ - Substitutei /g - Global
suma kontrolna
89
Uwaga dla użytkowników komputerów Mac, którzy dostają invalid command code Cbłąd ... W przypadku zamiany w miejscu BSD sedwymaga rozszerzenia pliku po oznaczeniu, -iponieważ zapisuje plik kopii zapasowej z danym rozszerzeniem. Na przykład: sed -i '.bak' 's/find/replace/' /file.txt Możesz pominąć tworzenie kopii zapasowej, używając pustego ciągu w następujący sposób:sed -i '' 's/find/replace/' /file.txt
Austin
8
Wskazówka: jeśli chcesz używać repalencji bez rozróżniania wielkości liters/abc/XYZ/gi
Boris D. Teoharov
166

Manipulowanie plikami zwykle nie jest wykonywane przez Bash, ale przez programy wywoływane przez Bash, np .:

perl -pi -e 's/abc/XYZ/g' /tmp/file.txt

-iFlaga informuje go zrobić wymianę w miejscu.

Zobacz man perlrunwięcej szczegółów, w tym jak wykonać kopię zapasową oryginalnego pliku.

Alnitak
źródło
37
Purysta we mnie mówi, że nie możesz być pewien, że Perl będzie dostępny w systemie. Ale obecnie tak rzadko się dzieje. Może pokazuję swój wiek.
szczupły
3
Czy możesz pokazać bardziej złożony przykład. Coś jak zamiana „chdir / blah” na „chdir / blah2”. Próbowałem perl -pi -e 's/chdir (?:\\/[\\w\\.\\-]+)+/chdir blah/g' text, ale wciąż pojawia się błąd z Brakiem spacji między wzorcem a następnym słowem jest przestarzały w wierszu 1-szym. Niezrównany (w wyrażeniu regularnym; oznaczony <- TUTAJ w m / (chdir) () (<- TUTAJ ?: \\ / at -e wiersz 1.
CMCDragonkai
@CMCDragonkai Sprawdź tę odpowiedź: stackoverflow.com/a/12061491/2730528
Alfonso Santiago
69

Byłem zaskoczony, kiedy potknąłem się o to ...

Istnieje pakiet, replacektóry jest dostarczany z "mysql-server"pakietem, więc jeśli go zainstalowałeś, wypróbuj:

# replace string abc to XYZ in files
replace "abc" "XYZ" -- file.txt file2.txt file3.txt

# or pipe an echo to replace
echo "abcdef" |replace "abc" "XYZ"

Zobacz man replacewięcej na ten temat.

rayro
źródło
12
Możliwe są tutaj dwie rzeczy: a) replacejest użytecznym niezależnym narzędziem, a ludzie MySQL powinni go wydać osobno i od niego polegać b) replacewymaga trochę MySQL o_O Tak czy inaczej, instalowanie serwera mysql w celu zastąpienia byłoby niewłaściwe :)
Philip Whitehouse
działa tylko na Mac? I w moim ubuntu CentOS, że komenda nie istnieje
Paul
1
To dlatego, że nie masz mysql-serverzainstalowanego pakietu. Jak wskazuje @rayro, replacejest częścią tego.
Phius
2
„Ostrzeżenie: zastąpienie jest przestarzałe i zostanie usunięte w przyszłej wersji”.
Steven Vachon
1
Uważaj, aby nie uruchomić polecenia REPLACE w systemie Windows! W systemie Windows polecenie REPLACE służy do szybkiej replikacji plików. Nie dotyczy tej dyskusji.
Maor
52

To jest stary post, ale dla każdego, kto chce używać zmiennych, ponieważ @centurian powiedział, że pojedyncze cudzysłowy oznaczają, że nic nie zostanie rozwinięte.

Prostym sposobem na uzyskanie zmiennych jest połączenie łańcuchów, ponieważ odbywa się to przez zestawienie w bash, co powinno działać:

sed -i -e "s/$var1/$var2/g" /tmp/file.txt
zcourts
źródło
39

Bash, podobnie jak inne powłoki, jest tylko narzędziem do koordynowania innych poleceń. Zazwyczaj próbowałbyś użyć standardowych poleceń UNIX, ale możesz oczywiście użyć Bash do wywołania czegokolwiek, w tym własnych skompilowanych programów, innych skryptów powłoki, skryptów Python i Perl itp.

W takim przypadku można to zrobić na kilka sposobów.

Jeśli chcesz odczytać plik i zapisać go w innym pliku, wykonując wyszukiwanie / zamianę w trakcie pracy, użyj sed:

sed 's/abc/XYZ/g' <infile >outfile

Jeśli chcesz edytować plik na miejscu (tak jakbyś otwierał plik w edytorze, edytuje go, a następnie zapisuje), dostarcz instrukcje do edytora liniowego „ex”

echo "%s/abc/XYZ/g
w
q
" | ex file

Ex jest jak vi bez trybu pełnoekranowego. Możesz nadać mu takie same polecenia, jak w wierszu polecenia vi: '.

szczupły
źródło
33

Znalazłem ten wątek między innymi i zgadzam się, że zawiera on najbardziej kompletne odpowiedzi, więc dodaję też mój:

  1. sedi edsą bardzo przydatne ... ręcznie. Spójrz na ten kod z @Johnny:

    sed -i -e 's/abc/XYZ/g' /tmp/file.txt
  2. Gdy moim ograniczeniem jest używanie go w skrypcie powłoki, nie można używać żadnej zmiennej zamiast „abc” lub „XYZ”. BashFAQ wydaje się zgodzić z tym, co rozumiem, co najmniej. Nie mogę więc użyć:

    x='abc'
    y='XYZ'
    sed -i -e 's/$x/$y/g' /tmp/file.txt
    #or,
    sed -i -e "s/$x/$y/g" /tmp/file.txt

    ale co możemy zrobić? Jak powiedział @Johnny, użyj, while read...ale niestety to nie koniec historii. Poniższe działało ze mną dobrze:

    #edit user's virtual domain
    result=
    #if nullglob is set then, unset it temporarily
    is_nullglob=$( shopt -s | egrep -i '*nullglob' )
    if [[ is_nullglob ]]; then
       shopt -u nullglob
    fi
    while IFS= read -r line; do
       line="${line//'<servername>'/$server}"
       line="${line//'<serveralias>'/$alias}"
       line="${line//'<user>'/$user}"
       line="${line//'<group>'/$group}"
       result="$result""$line"'\n'
    done < $tmp
    echo -e $result > $tmp
    #if nullglob was set then, re-enable it
    if [[ is_nullglob ]]; then
       shopt -s nullglob
    fi
    #move user's virtual domain to Apache 2 domain directory
    ......
  3. Jak widać, jeśli nullglobjest ustawiony, zachowuje się dziwnie, gdy istnieje ciąg zawierający a *:

    <VirtualHost *:80>
     ServerName www.example.com

    który staje się

    <VirtualHost ServerName www.example.com

    nie ma wspornika kąta końcowego, a Apache2 nie może nawet załadować.

  4. Ten rodzaj analizy powinien być wolniejszy niż wyszukiwanie za jednym naciśnięciem przycisku i zastępować, ale, jak już zauważyłeś, istnieją cztery zmienne dla czterech różnych wzorców wyszukiwania działających w jednym cyklu analizy.

Najbardziej odpowiednie rozwiązanie, jakie mogę wymyślić, biorąc pod uwagę założenia problemu.

centurian
źródło
12
W swoim (2) - możesz to zrobić sed -e "s/$x/$y/"i zadziała. Nie podwójne cytaty. Może stać się bardzo mylące, jeśli ciągi w samych zmiennych zawierają znaki o specjalnym znaczeniu. Na przykład jeśli x = „/” lub x = „\”. Kiedy trafisz na te problemy, prawdopodobnie oznacza to, że powinieneś przestać próbować używać powłoki do tego zadania.
szczupły
Cześć szczupły, widzę, że jesteś także przeciwny używaniu Perla. Jakie jest twoje rozwiązanie Ponieważ w rzeczywistości chcę dynamicznie zmieniać ścieżkę w pliku, co oznacza, że ​​mam dużo / w ciągu!
Mahdi
20

Możesz użyć sed:

sed -i 's/abc/XYZ/gi' /tmp/file.txt

Posługiwać się -i dla „ignorować sprawy”, jeśli nie jesteś pewny, aby znaleźć tekst jest abcalbo ABCalbo AbC, itp

Możesz użyć find a sedjeśli nie znasz swojej nazwy pliku:

find ./ -type f -exec sed -i 's/abc/XYZ/gi' {} \;

Znajdź i zamień we wszystkich plikach Python:

find ./ -iname "*.py" -type f -exec sed -i 's/abc/XYZ/gi' {} \;
MMParvin
źródło
12

Możesz także użyć edpolecenia, aby przeprowadzić wyszukiwanie w pliku i zastąpić:

# delete all lines matching foobar 
ed -s test.txt <<< $'g/foobar/d\nw' 

Zobacz więcej w „ Edycja plików za pomocą skryptów za pomocąed ”.

Blaszany Człowiek
źródło
3
To rozwiązanie jest niezależne od niezgodności GNU / FreeBSD (Mac OSX) (w przeciwieństwie do sed -i <pattern> <filename>). Bardzo dobrze!
Peterino,
11

Jeśli plik, nad którym pracujesz, nie jest tak duży, a tymczasowe przechowywanie go w zmiennej nie stanowi problemu, możesz użyć podstawienia ciągu Bash jednocześnie w całym pliku - nie ma potrzeby przechodzenia nad nim wiersz po wierszu:

file_contents=$(</tmp/file.txt)
echo "${file_contents//abc/XYZ}" > /tmp/file.txt

Cała zawartość pliku będzie traktowana jako jeden długi ciąg, łącznie z podziałami wiersza.

XYZ może być zmienną, np. $replacementJedną z zalet nieużywania sed jest to, że nie trzeba się martwić, że ciąg wyszukiwania lub zamiany może zawierać znak ogranicznika wzorca sed (zwykle, ale niekoniecznie, /). Wadą jest brak możliwości używania wyrażeń regularnych lub bardziej wyrafinowanych operacji sed.

johnraff
źródło
Jakieś wskazówki dotyczące używania tego ze znakami tabulacji? Z jakiegoś powodu mój skrypt nie znajduje niczego z tabulatorami po zmianie z sed z dużą ilością ucieczki do tej metody.
Brian Hannay,
2
Jeśli chcesz wstawić tabulację w zastępowanym ciągu, możesz to zrobić za pomocą składni „dollared single quotes” Basha, tak aby tabulator był reprezentowany przez $ '\ t' i możesz użyć $ echo 'tab' $ '\ t''separated>> plik testowy; $ file_contents = $ (<plik testowy); $ echo "$ {file_contents // $ '\ t' / TAB}"; tabTABseparated `
johnraff
5

Spróbuj wykonać następujące polecenie powłoki:

find ./  -type f -name "file*.txt" | xargs sed -i -e 's/abc/xyz/g'
J Ajay
źródło
4
To doskonała odpowiedź na pytanie „jak przypadkowo mogę również wszystkie pliki we wszystkich podkatalogach”, ale nie wydaje się, aby o to tutaj pytano.
tripleee
Ta składnia nie działa dla wersji BSD sed, użyj sed -i''zamiast tego.
kenorb
5

Aby edytować tekst w pliku bez interakcji, potrzebujesz lokalnego edytora tekstu, takiego jak vim.

Oto prosty przykład, jak go używać z wiersza poleceń:

vim -esnc '%s/foo/bar/g|:wq' file.txt

Jest to odpowiednik @slim odpowiedź z ex redaktor, który jest w zasadzie to samo.

Oto kilka expraktycznych przykładów.

Zastępowanie tekstu fooze barw pliku:

ex -s +%s/foo/bar/ge -cwq file.txt

Usuwanie końcowych spacji dla wielu plików:

ex +'bufdo!%s/\s\+$//e' -cxa *.txt

Rozwiązywanie problemów (gdy terminal jest zablokowany):

  • Dodaj -V1parametr, aby wyświetlić pełne wiadomości.
  • Siła rzucić przez: -cwq!.

Zobacz też:

kenorb
źródło
Chciał dokonać wymiany interaktywnie. Dlatego wypróbowałem "vim -esnc '% s / foo / bar / gc |: wq' file.txt". Ale terminal jest teraz zablokowany. Jak powinniśmy robić zamiany interaktywnie bez dziwnego zachowania się powłoki Bash.
vineeshvs,
Aby debugować, dodaj -V1, aby wymusić zamknięcie, użyj wq!.
kenorb
2

Możesz używać Pythona również w skrypcie bash. Nie udało mi się odnieść sukcesu z niektórymi najważniejszymi odpowiedziami tutaj i znalazłem, że zadziałało to bez potrzeby stosowania pętli:

#!/bin/bash
python
filetosearch = '/home/ubuntu/ip_table.txt'
texttoreplace = 'tcp443'
texttoinsert = 'udp1194'

s = open(filetosearch).read()
s = s.replace(texttoreplace, texttoinsert)
f = open(filetosearch, 'w')
f.write(s)
f.close()
quit()
micalith
źródło
1

Możesz użyć polecenia rpl. Na przykład chcesz zmienić nazwę domeny w całym projekcie php.

rpl -ivRpd -x'.php' 'old.domain.name' 'new.domain.name' ./path_to_your_project_folder/  

Nie jest to oczywiste uderzenie przyczyny, ale jest bardzo szybkie i przydatne. :)

zalex
źródło