Nahrazování řetězců v souborech na základě určitých kritérií vyhledávání je velmi běžný úkol. Jak mohu
- nahradit řetězec
foo
sbar
ve všech souborech v aktuálním adresáři? - provádět totéž rekurzivně pro podadresáře?
- nahradit pouze v případě, že název souboru odpovídá jinému řetězci?
- nahradit pouze v případě, že je řetězec nalezen v určitém kontextu?
- nahradit, pokud je řetězec na určitém čísle řádku?
- nahradit více řetězců stejnou náhradou
- nahradit více řetězců různými náhradami
Přijatá odpověď:
1. Nahrazení všech výskytů jednoho řetězce jiným ve všech souborech v aktuálním adresáři:
To jsou případy, kdy znáte že adresář obsahuje pouze běžné soubory a že chcete zpracovat všechny neskryté soubory. Pokud tomu tak není, použijte přístupy v 2.
Vše sed
řešení v této odpovědi předpokládají GNU sed
. Pokud používáte FreeBSD nebo macOS, nahraďte -i
s -i ''
. Všimněte si také, že použití -i
přepnout s jakoukoli verzí sed
má určité důsledky pro zabezpečení souborového systému a nedoporučuje se v žádném skriptu, který plánujete jakýmkoli způsobem distribuovat.
-
Nerekurzivní, soubory pouze v tomto adresáři:
sed -i -- 's/foo/bar/g' * perl -i -pe 's/foo/bar/g' ./*
(perl
jeden selže pro názvy souborů končící na |
nebo mezera)).
-
Rekurzivní, běžné soubory (včetně skrytých ) v tomto a všech podadresářích
find . -type f -exec sed -i 's/foo/bar/g' {} +
Pokud používáte zsh:
sed -i -- 's/foo/bar/g' **/*(D.)
(může selhat, pokud je seznam příliš velký, viz
zargs
obejít).Bash nemůže přímo kontrolovat běžné soubory, je potřeba smyčka (složené závorky se vyhýbají globálnímu nastavení možností):
( shopt -s globstar dotglob; for file in **; do if [[ -f $file ]] && [[ -w $file ]]; then sed -i -- 's/foo/bar/g' "$file" fi done )
Soubory jsou vybrány, když jsou skutečnými soubory (-f) a lze do nich zapisovat (-w).
2. Nahraďte pouze v případě, že název souboru odpovídá jinému řetězci / má specifickou příponu / je určitého typu atd:
-
Nerekurzivní, soubory pouze v tomto adresáři:
sed -i -- 's/foo/bar/g' *baz* ## all files whose name contains baz sed -i -- 's/foo/bar/g' *.baz ## files ending in .baz
-
Rekurzivní, běžné soubory v tomto a všech podadresářích
find . -type f -name "*baz*" -exec sed -i 's/foo/bar/g' {} +
Pokud používáte bash (složené závorky se vyhýbají globálnímu nastavení možností):
( shopt -s globstar dotglob sed -i -- 's/foo/bar/g' **baz* sed -i -- 's/foo/bar/g' **.baz )
Pokud používáte zsh:
sed -i -- 's/foo/bar/g' **/*baz*(D.) sed -i -- 's/foo/bar/g' **/*.baz(D.)
--
slouží ke sdělování sed
že v příkazovém řádku nebudou zadávány žádné další příznaky. To je užitečné pro ochranu před názvy souborů začínajícími -
.
-
Pokud je soubor určitého typu, například spustitelný (viz
man find
pro více možností):find . -type f -executable -exec sed -i 's/foo/bar/g' {} +
zsh
:
sed -i -- 's/foo/bar/g' **/*(D*)
3. Nahraďte pouze v případě, že je řetězec nalezen v určitém kontextu
-
Nahraďte
foo
sbar
pouze pokud existujebaz
později na stejném řádku:sed -i 's/foo(.*baz)/bar1/' file
V sed
pomocí ( )
uloží vše, co je v závorkách, a můžete k tomu přistupovat pomocí 1
. Existuje mnoho variant tohoto tématu. Chcete-li se o těchto regulárních výrazech dozvědět více, viz zde.
-
Nahraďte
foo
sbar
pouze pokudfoo
se nachází ve 3D sloupci (pole) vstupního souboru (za předpokladu, že pole oddělená mezerami):gawk -i inplace '{gsub(/foo/,"baz",$3); print}' file
(potřebuje gawk
4.1.0 nebo novější).
-
Pro jiné pole stačí použít
$N
kdeN
je číslo zájmového pole. Pro jiný oddělovač polí (:
v tomto příkladu) použijte:gawk -i inplace -F':' '{gsub(/foo/,"baz",$3);print}' file
Další řešení pomocí perl
:
perl -i -ane '$F[2]=~s/foo/baz/g; $" = " "; print "@Fn"' foo
POZNÁMKA:obě awk
a perl
řešení ovlivní mezery v souboru (odstraňte úvodní a koncové mezery a převeďte sekvence mezer na jeden znak mezery v řádcích, které se shodují). Pro jiné pole použijte $F[N-1]
kde N
je požadované číslo pole a pro použití jiného oddělovače polí ($"=":"
nastaví oddělovač výstupních polí na :
):
perl -i -F':' -ane '$F[2]=~s/foo/baz/g; $"=":";print "@F"' foo
-
Nahraďte
foo
sbar
pouze na 4. řádku:sed -i '4s/foo/bar/g' file gawk -i inplace 'NR==4{gsub(/foo/,"baz")};1' file perl -i -pe 's/foo/bar/g if $.==4' file
4. Operace vícenásobného nahrazení:nahrazení různými řetězci
-
sed
můžete kombinovat příkazy:sed -i 's/foo/bar/g; s/baz/zab/g; s/Alice/Joan/g' file
Uvědomte si, že na pořadí záleží (sed 's/foo/bar/g; s/bar/baz/g'
nahradí foo
s baz
).
-
nebo příkazy Perlu
perl -i -pe 's/foo/bar/g; s/baz/zab/g; s/Alice/Joan/g' file
-
Pokud máte velký počet vzorů, je snazší uložit vzory a jejich náhrady do
sed
soubor skriptu:#! /usr/bin/sed -f s/foo/bar/g s/baz/zab/g
-
Nebo, pokud máte příliš mnoho párů vzorů, než aby to bylo možné, můžete páry vzorů přečíst ze souboru (dva vzory oddělené mezerou, $vzor a $replacement, na řádek):
while read -r pattern replacement; do sed -i "s/$pattern/$replacement/" file done < patterns.txt
-
To bude poměrně pomalé pro dlouhé seznamy vzorů a velké datové soubory, takže možná budete chtít vzory přečíst a vytvořit
sed
místo toho skript od nich. Následující předpokládá <mezera> oddělovač odděluje seznam MATCH<mezera>NAHRADIT páry vyskytující se po jednom na řádek v souborupatterns.txt
:sed 's| *([^ ]*) *([^ ]*).*|s/1/2/g|' <patterns.txt | sed -f- ./editfile >outfile
Výše uvedený formát je do značné míry libovolný a například neumožňuje <mezeru> v jednom z MATCH nebo NAHRADIT . Tato metoda je však velmi obecná:v podstatě, pokud můžete vytvořit výstupní proud, který vypadá jako sed
skript, pak můžete tento stream získat jako sed
skript zadáním sed
soubor skriptu jako -
stdin.
-
Podobným způsobem můžete kombinovat a spojovat více skriptů:
SOME_PIPELINE | sed -e'#some expression script' -f./script_file -f- -e'#more inline expressions' ./actual_edit_file >./outfile
POSIX sed
zřetězí všechny skripty do jednoho v pořadí, v jakém se objeví na příkazovém řádku. Žádný z nich nemusí končit n
ewline.
-
grep
může fungovat stejným způsobem:sed -e'#generate a pattern list' <in | grep -f- ./grepped_file
-
Při práci s pevnými řetězci jako vzory je dobrým zvykem vyhnout se regulárnímu výrazu metaznaky . Můžete to udělat poměrně snadno:
sed 's/[]$&^*./[]/\&/g s| *([^ ]*) *([^ ]*).*|s/1/2/g| ' <patterns.txt | sed -f- ./editfile >outfile
5. Operace vícenásobného nahrazení:nahrazení více vzorů stejným řetězcem
-
Nahraďte libovolné z
foo
,bar
nebobaz
pomocífoobar
sed -Ei 's/foo|bar|baz/foobar/g' file
-
nebo
perl -i -pe 's/foo|bar|baz/foobar/g' file