Como substituir uma linha inteira em um arquivo de texto pelo número da linha

152

Eu tenho uma situação em que quero que um script bash substitua uma linha inteira em um arquivo. O número da linha é sempre o mesmo, de modo que pode ser uma variável codificada.

Não estou tentando substituir uma sub-string nessa linha, só quero substituir essa linha por uma nova.

Existem métodos bash para fazer isso (ou algo simples que possa ser lançado em um script .sh).

user788171
fonte

Respostas:

228

Não é o melhor, mas isso deve funcionar:

sed -i 'Ns/.*/replacement-line/' file.txt

onde Ndeve ser substituído pelo número da linha de destino. Isso substitui a linha no arquivo original. Para salvar o texto alterado em um arquivo diferente, solte a -iopção:

sed 'Ns/.*/replacement-line/' file.txt > new_file.txt
chepner
fonte
1
Existe uma maneira de fazer o sed modificar o arquivo em questão, em vez de despejar na tela?
user788171
8
Para mim, ele diz: sed: -e expression #1, char 26: unknown option to ``s'e minha linha é: sed -i '7s/.*/<param-value>http://localhost:8080/ASDF/services/REWS.REWSHttpSoap12Endpoint/</param-value>/' $TCE_SVN_HOME\trunk\tce\EWC\WebContent\WEB-INF\web.xml. Qualquer ideia?
Danijel
4
Cada um /no texto de substituição seria interpretado como a barra final do scomando, a menos que escapasse ( \/). A coisa mais fácil, porém, é escolher um caractere diferente e não utilizado como delimitador. Por exemplo sed -i '7s{.*}{<param-value>http://...}' $TCE_SVN_HOME/trunk...,.
chepner
4
Essa explicação é um pouco confusa e parece não funcionar. Se vocês tiverem problemas com o sed e seus recursos de configuração de delimitador, dê uma olhada nisto: grymoire.com/Unix/Sed.html#uh-2 Diz que o primeiro caractere por trás do sdetermina o delimitador. Então, para mudar o seu comando para usar, por exemplo, o #que seria assim:sed -i '7s#.*#<param-value>http://localhost:8080/ASDF/services/REWS.REWSHttpSoap12Endpo‌​int/</param-value>#'
func0der
7
Para expandir o @ meonlol's, o macOS requer um -eif -itambém. assim, o comando correto se torna:sed -e 'Ns/.*/replacement-line/' -i '' file.txt
ELLIOTTCABLE
44

Na verdade, eu usei esse script para substituir uma linha de código no arquivo cron nos servidores UNIX da nossa empresa há algum tempo. Nós o executamos como um shell script normal e não tivemos problemas:

#Create temporary file with new line in place
cat /dir/file | sed -e "s/the_original_line/the_new_line/" > /dir/temp_file
#Copy the new file over the original file
mv /dir/temp_file /dir/file

Isso não passa pelo número da linha, mas você pode alternar facilmente para um sistema baseado em número de linha colocando o número da linha antes do s/e colocando um curinga no lugar de the_original_line.

Kyle
fonte
17
Uso inútil de gato:sed -e "s/.../.../" /dir/file > /dir/temp_file
chepner
24
Inútil para o intérprete / compilador talvez, mas não para um ser humano que o lê. O código do @ Kyle lê bem da esquerda para a direita; o idioma que você usou IMO não, devido ao fato de o verbo estar antes do substantivo.
Clayton Stanley
1
Existe uma maneira de mesclar essas duas linhas, por exemplo, por algo como:sed -e "s/.../.../" /dir/file > /dir/file
Pubudu Dodangoda
22

Vamos supor que você queira substituir a linha 4 pelo texto "diferente". Você pode usar o AWK assim:

awk '{ if (NR == 4) print "different"; else print $0}' input_file.txt > output_file.txt

O AWK considera a entrada como "registros" divididos em "campos". Por padrão, uma linha é um registro. NRé o número de registros vistos. $0representa o registro completo atual (enquanto $1é o primeiro campo do registro e assim por diante; por padrão, os campos são palavras da linha).

Portanto, se o número da linha atual for 4, imprima a string "diferente", mas imprima a linha inalterada.

No AWK, o código do programa incluído { }é executado uma vez em cada registro de entrada.

Você precisa citar o programa AWK entre aspas simples para impedir que o shell tente interpretar coisas como o $0.

EDIT: Um programa AWK mais curto e mais elegante da @chepner nos comentários abaixo:

awk 'NR==4 {$0="different"} { print }' input_file.txt

Somente para o registro (ou seja, linha) número 4, substitua o registro inteiro pela string "diferente". Em seguida, para cada registro de entrada, imprima o registro.

Claramente, minhas habilidades no AWK estão enferrujadas! Obrigado, @chepner.

EDIT: e veja também uma versão ainda mais curta de @Dennis Williamson:

awk 'NR==4 {$0="different"} 1' input_file.txt

Como isso funciona é explicado nos comentários: o 1sempre avalia true, portanto, o bloco de código associado sempre é executado. Mas não há nenhum bloco de código associado, o que significa que o AWK faz sua ação padrão de apenas imprimir a linha inteira. O AWK foi projetado para permitir programas concisos como este.

steveha
fonte
3
Um pouco mais curto: awk 'NR==4 {$0="different"} { print }' input_file.txt.
chepner
2
@chepner: Um pouco mais curto:awk 'NR==4 {$0="different"}1' input_file.txt
Pausado até novo aviso.
@DennisWilliamson, eu nem sei como isso funciona! O que essa trilha 1faz? (Nota: Eu testei e realmente funciona Eu só não entendo como!.)
steveha
1
Imaginei que haveria uma maneira de abreviar a impressão incondicional! @stevaha: o 1 é apenas um valor verdadeiro, significando um "padrão" que sempre corresponde. E a ação padrão para uma correspondência é imprimir a linha atual.
chepner
19

Dado este arquivo de teste (test.txt)

Lorem ipsum dolor sit amet,
consectetur adipiscing elit. 
Duis eu diam non tortor laoreet 
bibendum vitae et tellus.

o comando a seguir substituirá a primeira linha por "texto de nova linha"

$ sed '1 c\
> newline text' test.txt

Resultado:

newline text
consectetur adipiscing elit. 
Duis eu diam non tortor laoreet 
bibendum vitae et tellus.

Mais informações podem ser encontradas aqui

http://www.thegeekstuff.com/2009/11/unix-sed-tutorial-append-insert-replace-and-count-file-lines/

Eric Robinson
fonte
2
Essa deve ser a resposta correta. O sinalizador c faz exatamente o que é necessário (substitui uma linha numerada pelo texto fornecido).
31415 adelphus
Sintaxe alternativa com sed que não ecoa o arquivo no stdout: stackoverflow.com/a/13438118/4669135
Gabriel Devillers
É a resposta certa, mas por que a linha feia quebra? sed '1 cnewline text' test.txtfaria isso também.
dev nulo
7

no bash, substitua N, M pelos números de linha e xxx yyy pelo que você deseja

i=1
while read line;do
  if((i==N));then
    echo 'xxx'
  elif((i==M));then
    echo 'yyy'
  else
    echo "$line"
  fi
  ((i++))
done  < orig-file > new-file

EDITAR

De fato, nesta solução, existem alguns problemas, com os caracteres "\ 0" "\ t" e "\"

"\ t", pode ser resolvido colocando IFS = antes da leitura: "\", no final da linha com -r

IFS= read -r line

mas para "\ 0", a variável está truncada, não há uma solução no bash puro: atribua string contendo caracteres nulos (\ 0) a uma variável no Bash Mas no arquivo de texto normal não há caracteres nulos \ 0

perl seria uma escolha melhor

perl -ne 'if($.==N){print"xxx\n"}elsif($.==M){print"yyy\n"}else{print}' < orig-file > new-file
Nahuel Fouilleul
fonte
Eu nem pensei em tentar uma solução BASH pura!
Steveha
4
# Replace the line of the given line number with the given replacement in the given file.
function replace-line-in-file() {
    local file="$1"
    local line_num="$2"
    local replacement="$3"

    # Escape backslash, forward slash and ampersand for use as a sed replacement.
    replacement_escaped=$( echo "$replacement" | sed -e 's/[\/&]/\\&/g' )

    sed -i "${line_num}s/.*/$replacement_escaped/" "$file"
}
Daniel Bigham
fonte
3

Excelente resposta da Chepner. Ele está trabalhando para mim no bash Shell.

 # To update/replace the new line string value with the exiting line of the file
 MyFile=/tmp/ps_checkdb.flag

 `sed -i "${index}s/.*/${newLine}/" $MyFile`

aqui
index- Linha não
newLine- nova string de linha que queremos substituir.


Da mesma forma, o código abaixo é usado para ler uma linha específica no arquivo. Isso não afetará o arquivo real.

LineString=`sed "$index!d" $MyFile` 

here
!d- excluirá as linhas que não sejam a linha no. $index Portanto, obteremos a saída como string de linha $indexno no arquivo.

Kanagavelu Sugumar
fonte
mas é minha festa porque o resultado só mostra ${newline}?
Sikisis
1

No mac eu usei

sed -i '' -e 's/text-on-line-to-be-changed.*/text-to-replace-the=whole-line/' file-name
nakeer
fonte
-2

Você pode até passar parâmetros para o comando sed:

test.sh

#!/bin/bash
echo "-> start"
for i in $(seq 5); do
  # passing parameters to sed
  j=$(($i+3))
  sed -i "${j}s/.*/replaced by '$i'!/" output.dat
done
echo "-> finished"
exit 

orignial output.dat:

a
b
c
d
e
f
g
h
i
j

A execução de ./test.sh fornece o novo output.dat

a
b
c
replaced by '1'!
replaced by '2'!
replaced by '3'!
replaced by '4'!
replaced by '5'!
i
j
AndreH
fonte
1
por que você precisa de um loop para este exemplo, apenas ofusca a solução? line=5; sed -i "${line}s/.*/replaced by '$i'!/" output.dat
Rob
Você não precisa de um loop para isso
chandu vutukuri 02/06