O comando é grep -rl 'old' . | xargs sed -i 's/old/new/g'. A metade importante é a primeira: grep -rl lista os arquivos que mudariam, então você pode olhar antes de o sed reescrevê-los no lugar.
1. Quais arquivos mudariam?
$ clai lista os arquivos dentro de proj que contêm old_name→ grep -rl 'old_name' proj/proj/src/b.pyproj/src/a.py
-r percorre em profundidade, -l imprime nomes de arquivo em vez das linhas que combinam. Dois arquivos — é um número plausível. Se dissesse 4000, seu padrão está amplo demais e você acabou de se poupar uma tarde.
2. Substituir exatamente nesses arquivos
$ clai substitua old_name por new_name em todos os arquivos dentro de proj que o contenham→ grep -rl 'old_name' proj/ | xargs sed -i 's/old_name/new_name/g'$ cat proj/src/a.pynew_name = 1
Só os arquivos correspondentes são abertos. Rodar sed -i na árvore inteira em vez disso reescreveria o mtime de cada arquivo do repositório, incluindo os milhares que não mudaram.
3. Nomes de arquivo com espaços não vão quebrar isso
$ clai mesma substituição, mas lidando com segurança com nomes de arquivo com espaços→ grep -rlZ 'old_name' proj/ | xargs -0 -r sed -i 's/old_name/new_name/g'
-Z faz o grep separar os nomes com um byte nulo e -0 faz o xargs esperar isso. Sem o par, meu arquivo.py chega como dois argumentos e o sed não edita nenhum deles. -r impede que o xargs rode o sed sem argumentos quando nada casou.
4. Pular os diretórios que você nunca quer tocar
$ clai substitua, mas pule node_modules, .git e vendor→ grep -rlZ --exclude-dir={.git,node_modules,vendor} 'old_name' . | xargs -0 -r sed -i 's/old_name/new_name/g'
Sem exclusões, você eventualmente vai reescrever o código-fonte de uma dependência, e o diff terá 40.000 linhas.
5. Só um tipo de arquivo
$ clai substitua só em arquivos python→ grep -rlZ --include='*.py' 'old_name' . | xargs -0 -r sed -i 's/old_name/new_name/g'
--include é o gêmeo de lista branca do --exclude. Quanto mais estreito, mais seguro.
6. Manter um backup — e continuar portátil
$ clai faça a substituição mas mantenha um backup de cada arquivo, funcionando no mac também→ grep -rlZ 'old_name' proj/ | xargs -0 -r sed -i.bak 's/old_name/new_name/g'
sed -i.bak grava a.py.bak ao lado de cada arquivo editado. É também a única forma que funciona tanto no sed GNU quanto no BSD — o macOS exige o sufixo, o Linux o aceita.
Pegadinhas
- Seu padrão é uma regex, não uma string.
.,*,/e$são especiais. Substituirconfig.ymltambém casa comconfigXyml; escape-o ou use um delimitador que não esteja no texto:sed -i 's|old/path|new/path|g'. - Faça commit primeiro.
sed -inão tem desfazer. Um git status limpo antes da substituição transforma um erro emgit checkout .. grep -lpara na primeira ocorrência por arquivo — é isso que o torna rápido, e não afeta a substituição, já que osedcontinua processando todas as linhas.
Perguntas relacionadas
Como eu pré-visualizo a substituição em vez de aplicá-la? Tire o -i e deixe imprimir: grep -rlZ 'old' . | xargs -0 sed 's/old/new/g' | less. Ou grep -rn 'old' . para ver cada linha com seu número.
Por que nada aconteceu? Geralmente o xargs recebeu uma lista vazia, ou seu padrão não casa com o que você pensa. Rode só a metade com grep -rl.
Dá para fazer com find em vez disso? Sim: find . -type f -name '*.py' -exec sed -i 's/old/new/g' {} +. Ele reescreve cada arquivo correspondente, tenha ou não o padrão, por isso a versão com grep -rl é preferível.
Veja também
- Renomear centenas de arquivos: de loops
fora linguagem natural sed -inão funciona no Mac? Diferenças BSD vs GNU, resolvidas- De um-liner a script versionado: quando vale a pena
Descreva a substituição, leia o comando que o CliAI escreve, depois rode. Instale em uma linha.