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.


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)
#

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' {} +

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)

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.


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 .


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*')

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.


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**/'

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 .

Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.