Faça sed pedir confirmação antes de cada substituição?

37

Existe uma maneira de fazer com que o sed me peça uma confirmação antes de cada substituição? Algo semelhante ao 'c' ao usar substituir dentro do vim.

O sed faz isso?

Yuvi
fonte
3
Seria tecnicamente possível, mas mais um exercício intelectual do que um esforço útil. Consulte Como fazer uma substituição de texto em uma hierarquia de pastas grandes? que tem soluções Vim e Perl.
Gilles 'SO- stop be evil'
Eu estou usando vim (args e argdo) sempre que eu preciso 'confirmação', mas queria saber se havia uma maneira 'mais simples'
Yuvi
1
Isso vai contra o objetivo básico do sed - automatizar a edição em um fluxo.
Teppic # 1/15
@teppic não porque você ainda precisa encontrar as instâncias nos arquivos de texto, o que o sed pode fazer por você. Eu acho que a pergunta faz sentido, apenas no caso de você adicionou os arquivos errados para uma lista e queria ver o arquivo que você estava editando
Kolob Canyon

Respostas:

37

Fazê-lo com sedprovavelmente não seria possível, pois ele é um editor de fluxo não-interativo. A quebra sedde um script exigiria muito pensamento. É mais fácil fazer isso com vim:

vim -c '%s/PATTERN/REPLACEMENT/gc' -c 'wq' file.in

Como foi mencionado nos comentários abaixo, veja como isso seria usado em vários arquivos que correspondam a um padrão de globbing de nome de arquivo específico no diretório atual:

for fname in file*.txt; do
    vim -c '%s/PATTERN/REPLACEMENT/gc' -c 'wq' "$fname"
done

Ou, se você deseja primeiro garantir que o arquivo realmente contenha uma linha que corresponda ao padrão especificado primeiro, antes de executar a substituição,

for fname in file*.txt; do
    grep -q 'PATTERN' "$fname" &&
    vim -c '%s/PATTERN/REPLACEMENT/gc' -c 'wq' "$fname"
done

Os dois loops de shell acima modificados em findcomandos que fazem as mesmas coisas, mas para todos os arquivos com um nome específico em algum lugar ou sob algum top-dirdiretório,

find top-dir -type f -name 'file*.txt' \
    -exec vim -c '%s/PATTERN/REPLACEMENT/gc' -c 'wq' {} \;
find top-dir -type f -name 'file*.txt' \
    -exec grep -q 'PATTERN' {} \; \
    -exec vim -c '%s/PATTERN/REPLACEMENT/gc' -c 'wq' {} \;

Ou, usando os loops de shell originais e tendo findnomes de caminho neles:

find top-dir -type f -name 'file*.txt' -exec sh -c '
    for pathname do
        vim -c "%s/PATTERN/REPLACEMENT/gc" -c "wq" "$pathname"
    done' sh {} +
find top-dir -type f -name 'file*.txt' -exec sh -c '
    for pathname do
        grep -q "PATTERN" "$pathname" &&
        vim -c "%s/PATTERN/REPLACEMENT/gc" -c "wq" "$pathname"
    done' sh {} +

De qualquer forma, você não quer fazer algo como for filename in $( grep -rl ... )desde

  1. exigiria que a grepexecução fosse executada antes mesmo de iniciar a primeira iteração do loop, que é deselegante , e
  2. os nomes de caminho retornados por grepseriam divididos em palavras em espaços em branco, e essas palavras sofreriam um globbing de nome de arquivo (isso desqualifica nomes de caminho que contêm espaços e caracteres especiais).

Palavras-chave:

Kusalananda
fonte
2
A vantagem sedé que ele pode operar em vários arquivos, por exemplo, sed -i 's/old/new/g' /path/to/*.txtou algo semelhante.
User1717828
10
for i in $(grep -rl "old"); do vim -c "%s/old/new/gc" -c "wq" "$i"; done
Tcpaiva 14/08/16
@ecepe, converta-o em uma resposta, obrigado.
Nishant
1
@ Nishant Seria uma resposta frágil, pois desqualificaria qualquer arquivo que contenha espaço em branco em seu nome ou caminho.
Kusalananda
7

Você pode obter isso fazendo o seguinte:

:%s/OLD_TEXT/NEW_TEXT/gc

Especificamente, adicionando o cdepois do terceiro delimitador.

Observe que a opção 'c' funciona apenas no Vim; você não poderá usá-lo sedna linha de comando.

Kevin M
fonte
4
Para esclarecer: isso só funciona no Vim, não na linha de comando regular sed.
Brendan
O OP está marcado com vim, portanto, esta resposta é aplicável; no entanto, claramente vim e sed têm habilidades diferentes
ILMostro_7 13/03
-1
searchandreplace () {
if [ -z $2 ] ; then
    realpath * | xargs grep -ins --directories=skip --color=always "$1" | nl -s "."
else
    realpath * | xargs grep -ins --directories=skip --color=always "$1" | nl -s "."
    exp=$(realpath * | xargs grep -ins --directories=skip --color=never "$1" | cut -d':' -f1,2 | awk -F':' '{print $2 $1}' | sed -E "s|^[0-9]+|sed -i \'&s/|; s|//|/"$1"/"$2"/g\' /|")
    IFS=$'\n'
    for d in $exp
        do
        read -p "Exec $(echo -e -n "\033[1;31m$d\033[0m")? " -e REP
            if [ "$REP" = y ]; then
            echo `bash -c $d`;
            fi
    done
fi
}

Se você alimenta isso com argumento único - searchandreplace "AAA"- ele procura apenas essa sequência no diretório atual, mas se você alimenta com argumentos duplos - searchandreplace AAA BBB- além do acima, ele também solicita que você substitua a primeira pela segunda "linha por linha "para cada linha.

LyXTeX
fonte