Com ferramentas GNU
< dir.list xargs -rd '\n' grep -rlZ -- "$searchstring" |
xargs -r0 sed -i -e "s/$searchstring/$replacestring/ig" --
(Não se esqueça de citar suas variáveis, deixando uma variável sem aspas é o operador split + glob)
Eu criei o script abaixo, que pega o caminho de um único diretório e substitui a string de pesquisa em todos os arquivos desse diretório. Gostaria de aprimorar esse script de forma que ele possa pesquisar e substituir a sequência em vários diretórios listados em um arquivo de entrada externo.
Conteúdo do arquivo de entrada externo:
/var/start/system1/dir1
/var/start/system2/dir2
/var/start/system3/dir3
/var/start/system4/dir4
Script com um diretório:
filepath="/var/start/system/dir1"
searchstring="test"
replacestring="test01"
i=0;
for file in $(grep -l -R $searchstring $filepath)
do
cp $file $file.bak
sed -e "s/$searchstring/$replacestring/ig" $file > tempfile.tmp
mv tempfile.tmp $file
let i++;
echo "Modified: " $file
done
Com ferramentas GNU
< dir.list xargs -rd '\n' grep -rlZ -- "$searchstring" |
xargs -r0 sed -i -e "s/$searchstring/$replacestring/ig" --
(Não se esqueça de citar suas variáveis, deixando uma variável sem aspas é o operador split + glob)
Primeiro de tudo, a dança tmpfile pode ser evitada usando sed -i com o GNU sed ou sed -i '' com o FreeBSD (substituição no local).
grep -R pode ter vários caminhos na linha de comando, portanto, se você tiver certeza de que nenhum dos caminhos contém espaços, poderá substituir $(grep -l -Re "$searchstring" "$filepath") por $(grep -l -R "$searchstring" $(cat path_list)) .
Isso falhará se algum dos caminhos contiver espaços, tabulações ou qualquer caractere globbing, mas também o original.
Uma abordagem muito mais robusta usa find e apenas aplica sed a todos os arquivos, confiando que não modifique arquivos sem correspondência (assumindo aqui um bash shell):
# Read newline-separated path list into array from file 'path_list'
IFS=$'\n' read -d '' -r -a paths path_list
# Run sed on everything
find "${paths[@]}" \
-exec sed -i -r -e "s/$searchstring/$replacestring/ig" '{}' ';'
Mas isso não fornece comentários sobre quais arquivos estão sendo modificados.
Uma versão mais longa que fornece o feedback:
# Read newline-separated path list into array from file 'path_list'
IFS=$'\n' read -d '' -r -a paths path_list
grep -l -R "$searchstring" "${paths[@]}" | while IFS= read -r file; do
sed -r -i -e "s/$searchstring/$replacestring/ig" "$file"
echo "Modified: $file"
done
Esta é a maneira mais portátil que posso pensar em fazer isso, embora ainda dependa do principalmente portátil /dev/fd/0 para .dot . Sem isso, você poderia usar um único arquivo. Em qualquer caso, depende principalmente desta função de shell que escrevi no outro dia:
_sed_cesc_qt() {
sed -n ':n;\|^'"$1"'|!{H;$!{n;bn}};{$l;x;l}' |
sed -n '\|^'"$1"'|{:n;\|[$]$|!{
N;s|.\n||;bn};s|||
\|\([^\]\)\\([0-9]\)|{
s||\0|g;}'"
s|'"'|&"&"&|g;'"s|.*|'&'|p}"
}
Primeiro mostrarei o trabalho, depois explicarei como. Então, vou criar uma base de arquivos de teste:
printf 'f=%d
echo "$f" >./"$f"
echo "$f" >./"$f\n$f"
echo "$f" >./"$f\n$f\n$f"
' $(seq 10) | . /dev/fd/0
Isso cria um monte de arquivos, cada um nomeado para o número 1-10 que ele contém:
ls -qm
1, 1?1, 1?1?1, 10, 10?10, 10?10?10, 2, 2?2, 2?2?2, 3, 3?3, 3?3?3, 4, 4?4, 4?4?4, 5, 5?5, 5?5?5, 6, 6?6, 6?6?6, 7,
7?7, 7?7?7, 8, 8?8, 8?8?8, 9, 9?9, 9?9?9
Essa é uma lista delimitada por vírgulas dos arquivos no meu diretório de teste, cada ? representando uma nova linha.
cat ./1*
1
1
1
10
10
10
Cada arquivo contém apenas um único número.
Agora eu farei o grep replace:
find ././ \! -type d -exec \
grep -l '[02468]$' \{\} + |
_sed_cesc_qt '\./\./' |
sed 's|.|\&|g' |
xargs printf 'f=%b
sed "/[02468]\$/s//CHANGED/" <<-SED >"$f"
$(cat <"$f")
SED\n' |
. /dev/fd/0
Agora, quando eu ...
cat ./1*
1
1
1
1CHANGED
1CHANGED
1CHANGED
Todos os arquivos [2468] são similarmente CHANGED . Ele funciona recursivamente também. Ok, agora vou explicar como.
Primeiro, eu acho, a função:
:n ext label \| address | argumento $1 - um marcador ! não compatível {
H buffer antigo ! não $ última linha {
n ext line b ranch de volta para :n ext label }} $ última linha l ook no espaço padrão x mudam o conteúdo dos buffers de retenção e padrão e ... l inequivocamente no espaço padrão Essa é a primeira declaração sed - e é basicamente a carne e batatas dela. Nós nunca p rint o espaço padrão - nós apenas l ook nele. É assim que o POSIX define a função l :
[2addr]
l(The letter ell.) Write the pattern space to standard output in a visually unambiguous form. The characters listed in the Base Definitions volume of IEEE Std 1003.1-2001, Table 5-1, Escape Sequences and Associated Actions( '\', '\a', '\b', '\f', '\r', '\t', '\v' )shall be written as the corresponding escape sequence; the'\n'in that table is not applicable. Non-printable characters not in that table shall be written as one three-digit octal number (with a preceding\backslash) for each byte in the character (most significant byte first). Long lines shall be folded, with the point of folding indicated by writing a\backslash followed by a\newline; the length at which folding occurs is unspecified, but should be appropriate for the output device. The end of each line shall be marked with a'$'.
Então, se eu fizer isso:
printf '\e%s10\n10\n10' '\' | sed -n 'N;N;l'
Eu recebo:
3\10\n10\n10$
Isso é quase perfeitamente escapado para printf . Ele precisa apenas de um zero extra para o octal e para remover o $ - assim, a próxima instrução sed o limpa.
Eu não vou fazer o mesmo nível de detalhe, mas basicamente a próxima declaração sed :
$1 marker ... N ext até que a linha atual termine em $
\ e \n . $ final
\ seguidas por um número que não é precedido por outra barra invertida \ e insere um zero ' e aspas duplas ' aspas simples Então agora, quando eu faço:
printf %s\n ././1* |
_sed_cesc_qt '\./\./'
Eu recebo:
'././1'
'././1\n1'
'././1\n1\n1'
'././10'
'././10\n10'
'././10\n10\n10'
O resto é fácil. Depende do fato de que a string ././ será resolvida, mas ocorrerá apenas na saída de find/grep na cabeça de cada nome de caminho - portanto, ela se tornará meu marcador $1 .
Eu -exec grep de find e especifique -l para que ele envie nomes de arquivos para os arquivos que contenham o regex.
Eu chamo a função e obtenho sua saída.
Eu, então, \ barra invertida escape de todos os caracteres em sua saída para xargs .
E com printf eu escrevo um script para o arquivo |pipe - que eu .dot source como /dev/fd/0 . Eu defino a variável f como seu argumento atual - meu nome de caminho - e cat that $f argumento para um << heredocument, que é alimentado para sed e sed escreve sobre o arquivo de origem.
Isso pode envolver arquivos temporários - isso depende do seu shell. bash e zsh escreverão um arquivo temporário para cada documento - mas também os limpam. dash , por outro lado, apenas escreverá o heredocument em anônimo |pipe .