Zmień wiele plików

194

Następujące polecenie poprawnie zmienia zawartość 2 plików.

sed -i 's/abc/xyz/g' xaa1 xab1 

Ale muszę dynamicznie zmieniać kilka takich plików i nie znam ich nazw. Chcę napisać polecenie, które odczyta wszystkie pliki z bieżącego katalogu, zaczynając od xa*i sedpowinno zmienić zawartość pliku.

shantanuo
źródło
62
Masz na myśli sed -i 's/abc/xyz/g' xa*?
Paul R
3
Odpowiedzi tutaj nie wystarczą. Zobacz unix.stackexchange.com/questions/112023/…
Isaac

Odpowiedzi:

136

Jeszcze lepiej:

for i in xa*; do
    sed -i 's/asd/dfg/g' $i
done

ponieważ nikt nie wie, ile jest plików i łatwo jest przekroczyć limity wiersza poleceń.

Oto, co dzieje się, gdy jest zbyt wiele plików:

# grep -c aaa *
-bash: /bin/grep: Argument list too long
# for i in *; do grep -c aaa $i; done
0
... (output skipped)
#
Lenik
źródło
18
Jeśli jest tak wiele plików, przekroczysz limit wiersza poleceń w forpoleceniu. Aby się przed tym zabezpieczyć, musisz użyćfind ... | xargs ...
glenn jackman
1
Nie znam implementacji, ale wzorzec „xa *” musi w pewnym momencie zostać rozszerzony. Czy powłoka robi rozszerzenie inaczej forniż dla echolub grep?
glenn jackman
4
zobacz zaktualizowaną odpowiedź. jeśli potrzebujesz więcej informacji, zadaj oficjalne pytanie, aby inni mogli Ci pomóc.
lenik
5
W poleceniu sed należy użyć "$i"zamiast, $iaby uniknąć podziału słów na nazwy plików ze spacjami. W przeciwnym razie jest to bardzo miłe.
Wildcard,
4
Jeśli chodzi o listę, uważam, że różnica polega na tym, że forjest ona częścią składni języka, a nie tylko wbudowanym. Ponieważ sed -i 's/old/new' *rozszerzenie *ALL musi zostać przekazane jako arglista do sed, i jestem całkiem pewien, że musi się to zdarzyć, zanim sedproces będzie mógł zostać rozpoczęty. Za pomocą forpętli pełny arglista (rozszerzenie *) nigdy nie jest przekazywany jako polecenie, tylko przechowywany w pamięci powłoki i iterowany. W ogóle nie mam na to odniesienia, wydaje się prawdopodobne, że taka jest różnica. (Chciałbym usłyszeć od kogoś bardziej kompetentnego ...)
Wildcard
166

Dziwię się, że nikt nie wspomniał o argumencie -exec do znalezienia, który jest przeznaczony dla tego typu przypadku użycia, chociaż uruchomi on proces dla każdej pasującej nazwy pliku:

find . -type f -name 'xa*' -exec sed -i 's/asd/dsg/g' {} \;

Alternatywnie można użyć xargs, który wywoła mniej procesów:

find . -type f -name 'xa*' | xargs sed -i 's/asd/dsg/g'

Lub, po prostu, użyj + wariantu exec zamiast ;w find, aby umożliwić findowi udostępnienie więcej niż jednego pliku na wywołanie podprocesu:

find . -type f -name 'xa*' -exec sed -i 's/asd/dsg/g' {} +
ealfonso
źródło
7
Musiałem zmodyfikować polecenie w tej odpowiedzi w ten sposób: find ./ -type f -name 'xa*' -exec sed -i '' 's/asd/dsg/g' {} \;to jest lokalizacja polecenia find ./i para pojedynczych cudzysłowów -idla OSX.
shelbydz
Polecenie find działa tak, jak zostało dostarczone przez ealfonso, ./jest równe .i -ima tylko parametr backupsuffix.
uhausbrand
-execOpcja znalezisku wraz ze {} +wystarczy, aby rozwiązać problem, jak wspomniano, i powinno być w porządku dla większości wymagań. Ale xargsogólnie jest to lepszy wybór, ponieważ umożliwia również równoległe przetwarzanie z tą -popcją. Gdy globalna ekspansja jest wystarczająco duża, aby przepełnić długość linii poleceń, prawdopodobnie odniesiesz również korzyść z przyspieszenia w sekwencyjnym przebiegu.
Amit Naidu
78

Możesz użyć grep i sed razem. To pozwala przeszukiwać podkatalogi rekurencyjnie.

Linux: grep -r -l <old> * | xargs sed -i 's/<old>/<new>/g'
OS X: grep -r -l <old> * | xargs sed -i '' 's/<old>/<new>/g'

For grep:
    -r recursively searches subdirectories 
    -l prints file names that contain matches
For sed:
    -i extension (Note: An argument needs to be provided on OS X)
Raj
źródło
3
Premią tej metody dla mnie było to, że mogłem wsunąć się, grep -vaby uniknąć folderów gitgrep -rl <old> . | grep -v \.git | xargs sed -i 's/<old>/<new>/g'
Martin Lyne
najlepsze rozwiązanie dla Maca!
Markiz Blount
30

Te polecenia nie będą działać domyślnie sedw systemie Mac OS X.

Od man 1 sed:

-i extension
             Edit files in-place, saving backups with the specified
             extension.  If a zero-length extension is given, no backup 
             will be saved.  It is not recommended to give a zero-length
             extension when in-place editing files, as you risk corruption
             or partial content in situations where disk space is exhausted, etc.

Wypróbowany

sed -i '.bak' 's/old/new/g' logfile*

i

for i in logfile*; do sed -i '.bak' 's/old/new/g' $i; done

Oba działają dobrze.

funroll
źródło
2
@sumek Oto przykładowa sesja terminalowa w systemie OS X, która pokazuje sed zastępujący wszystkie wystąpienia: GitHub Gist
funroll
Użyłem tego, aby zastąpić dwa różne wiersze we wszystkich plikach konfiguracyjnych mojej witryny linkiem poniżej. sed -i.bak "s / supercache_proxy_config / proxy_includes \ / supercache_config / g; s / basic_proxy_config / proxy_include \ / basic_proxy_config / g" strony dostępne / * Nie zapomnij usunąć plików * .bak po zakończeniu tworzenia pliku system higieny.
Josiah
19

@PaulR opublikował to jako komentarz, ale ludzie powinni postrzegać to jako odpowiedź (i ta odpowiedź najlepiej pasuje do moich potrzeb):

sed -i 's/abc/xyz/g' xa*

Będzie to działać dla umiarkowanej liczby plików, prawdopodobnie rzędu dziesiątek, ale prawdopodobnie nie rzędu milionów .

palswim
źródło
Załóżmy, że masz zamienniki. Kolejny przykład z ścieżkami plików sed -i 's|auth-user-pass nordvpn.txt|auth-user-pass /etc/openvpn/nordvpn.txt|g' *.ovpn.
Léo Léopold Hertz 준영
10

Innym bardziej wszechstronnym sposobem jest użycie find:

sed -i 's/asd/dsg/g' $(find . -type f -name 'xa*')
dkinzer
źródło
1
dane wyjściowe tego polecenia find zostają rozszerzone, więc to nie rozwiązuje problemu. Zamiast tego powinieneś użyć -exec
ealfonso
@erjoalgo to działa, ponieważ polecenie sed obsługuje wiele plików wejściowych. Konieczne jest rozszerzenie komendy find, aby działało.
dkinzer
działa tak długo, jak długo liczba plików nie przekracza limitów wiersza poleceń.
ealfonso
Limit ten zależy tylko od zasobów pamięci dostępnych dla komputera i jest dokładnie taki sam jak limit dla exec.
dkinzer
4
To po prostu nieprawda. W powyższym poleceniu $ (find. ...) zostaje rozwinięte w jedno polecenie, które może być bardzo długie, jeśli istnieje wiele pasujących plików. Jeśli jest za długi (na przykład w moim systemie limit wynosi około 2097152 znaków), możesz otrzymać błąd: „Lista argumentów za długa” i polecenie się nie powiedzie. Proszę google ten błąd, aby uzyskać trochę tła na ten temat.
ealfonso
2

Używam finddo podobnego zadania. Jest to dość proste: musisz podać to jako argument za sedtym:

sed -i 's/EXPRESSION/REPLACEMENT/g' `find -name "FILE.REGEX"`

W ten sposób nie musisz pisać skomplikowanych pętli i łatwo jest sprawdzić, które pliki zamierzasz zmienić, po prostu uruchom findprzed uruchomieniem sed.

Bluesboy
źródło
1
Jest to dokładnie to samo, co odpowiedź @ dkinzera .
Pan Tao,
0

możesz zrobić

Wyszukaj tekst „ xxxx ” i zastąpisz go tekstem „ rrrr

grep -Rn '**xxxx**' /path | awk -F: '{print $1}' | xargs sed -i 's/**xxxx**/**yyyy**/'
Mohamed Galal
źródło
0

Jeśli możesz uruchomić skrypt, oto co zrobiłem dla podobnej sytuacji:

Używając słownika / hashMap (tablicy asocjacyjnej) i zmiennych dla sedpolecenia, możemy zapętlać tablicę, zastępując kilka ciągów. Umieszczenie symbolu wieloznacznego w name_patternpliku pozwoli zastąpić w miejscu pliki wzorkiem (może to być coś podobnego name_pattern='File*.txt') w określonym katalogu ( source_dir). Wszystkie zmiany są zapisane wlogfile wdestin_dir

#!/bin/bash
source_dir=source_path
destin_dir=destin_path
logfile='sedOutput.txt'
name_pattern='File.txt'

echo "--Begin $(date)--" | tee -a $destin_dir/$logfile
echo "Source_DIR=$source_dir destin_DIR=$destin_dir "

declare -A pairs=( 
    ['WHAT1']='FOR1'
    ['OTHER_string_to replace']='string replaced'
)

for i in "${!pairs[@]}"; do
    j=${pairs[$i]}
    echo "[$i]=$j"
    replace_what=$i
    replace_for=$j
    echo " "
    echo "Replace: $replace_what for: $replace_for"
    find $source_dir -name $name_pattern | xargs sed -i "s/$replace_what/$replace_for/g" 
    find $source_dir -name $name_pattern | xargs -I{} grep -n "$replace_for" {} /dev/null | tee -a $destin_dir/$logfile
done

echo " "
echo "----End $(date)---" | tee -a $destin_dir/$logfile

Najpierw deklarowana jest tablica par, każda para jest ciągiem zastępującym, a następnie WHAT1zostanie zastąpiona FOR1i OTHER_string_to replacezostanie zastąpiona string replacedw pliku File.txt. W pętli tablica jest odczytywana, pierwszy element z pary jest pobierany jako, replace_what=$ia drugi jako replace_for=$j. Thefind wyszukiwania poleceń w katalogu nazwa pliku (które mogą zawierać symbol wieloznaczny) i sed -izastępuje polecenie w jaki został wcześniej zdefiniowany tego samego pliku (ów). W końcu dodałem grepprzekierowanie do pliku dziennika, aby zarejestrować zmiany dokonane w pliku (plikach).

To działało dla mnie GNU Bash 4.3 sed 4.2.2i oparte na odpowiedzi VasyiNovikov dla Loop na krotki w bash .

Lejuanjowski
źródło