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\n
ewline; 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
.