Jak zamienić cały wiersz w pliku tekstowym na numer wiersza

152

Mam sytuację, w której chcę, aby skrypt bash zastąpił całą linię w pliku. Numer wiersza jest zawsze taki sam, więc może to być zmienna zakodowana na stałe.

Nie próbuję zamienić jakiegoś podciągu w tym wierszu, po prostu chcę całkowicie zastąpić ten wiersz nowym wierszem.

Czy są do tego jakieś metody basha (lub coś prostego, które można wrzucić do skryptu .sh).

user788171
źródło

Odpowiedzi:

228

Nie najlepszy, ale to powinno działać:

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

gdzie Nnależy zastąpić docelowym numerem linii. Zastępuje wiersz w oryginalnym pliku. Aby zapisać zmieniony tekst w innym pliku, upuść -iopcję:

sed 'Ns/.*/replacement-line/' file.txt > new_file.txt
Chepner
źródło
1
Czy istnieje sposób, aby sed zmodyfikował dany plik zamiast zrzucać go na ekran?
user788171
8
Dla mnie jest napisane: sed: -e expression #1, char 26: unknown option to ``s'a moja linia jest: 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. Dowolny pomysł?
Danijel
4
Każdy /w tekście zastępczym byłby interpretowany jako zamykający ukośnik spolecenia, chyba że jest to znak ucieczki ( \/). Najłatwiej jednak jest wybrać inny, nieużywany znak jako separator. Na przykład sed -i '7s{.*}{<param-value>http://...}' $TCE_SVN_HOME/trunk....
chepner
4
To wyjaśnienie jest trochę zagmatwane i nie wydaje się działać. Jeśli macie problemy z sedem i jego możliwościami ustawiania separatora, możecie rzucić okiem na to: grymoire.com/Unix/Sed.html#uh-2 Mówi, że pierwszy znak za znakiem sokreśla separator. Tak więc, aby zmienić polecenie, aby użyć na przykład polecenia #, wyglądałoby to tak:sed -i '7s#.*#<param-value>http://localhost:8080/ASDF/services/REWS.REWSHttpSoap12Endpo‌​int/</param-value>#'
func0der
7
Aby rozwinąć @ meonlol, macOS wymaga również podania -eif -i; tak więc poprawne polecenie to:sed -e 'Ns/.*/replacement-line/' -i '' file.txt
ELLIOTTCABLE
44

Właściwie użyłem tego skryptu do zastąpienia linii kodu w pliku cron na serwerach UNIX naszej firmy. Wykonaliśmy to jako normalny skrypt powłoki i nie mieliśmy żadnych problemów:

#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

Nie chodzi o numer linii, ale możesz łatwo przełączyć się na system oparty na numerach linii, umieszczając numer linii przed s/znakiem i zastępując go symbolem wieloznacznym the_original_line.

Kyle
źródło
17
Bezużyteczne wykorzystanie kota:sed -e "s/.../.../" /dir/file > /dir/temp_file
chepner
24
Może bezużyteczne dla interpretera / kompilatora, ale nie dla człowieka czytającego. Kod @ Kyle'a ładnie czyta się od lewej do prawej; idiom, którego użyłeś IMO, nie jest, ze względu na fakt, że czasownik występuje przed rzeczownikiem.
Clayton Stanley
1
Czy jest sposób, aby połączyć te dwie linie, na przykład przez coś takiegosed -e "s/.../.../" /dir/file > /dir/file
Pubudu Dodangoda
22

Załóżmy, że chcesz zastąpić wiersz 4 tekstem „inny”. Możesz używać AWK w następujący sposób:

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

AWK traktuje dane wejściowe jako „rekordy” podzielone na „pola”. Domyślnie jeden wiersz to jeden rekord. NRto liczba wyświetlonych rekordów. $0reprezentuje bieżący pełny rekord (gdzie $1jest pierwszym polem z rekordu i tak dalej; domyślnie pola są słowami z wiersza).

Tak więc, jeśli bieżący numer wiersza to 4, wypisuje łańcuch „inny”, ale w przeciwnym razie wypisuje wiersz bez zmian.

W AWK kod programu zawarty w jest { }uruchamiany raz na każdym rekordzie wejściowym.

Musisz cytować program AWK w pojedynczych cudzysłowach, aby powłoka nie próbowała interpretować rzeczy takich jak $0.

EDYCJA: Krótszy i bardziej elegancki program AWK od @chepner w komentarzach poniżej:

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

Tylko dla rekordu (tj. Linii) numer 4, zastąp cały rekord ciągiem „różne”. Następnie wydrukuj rekord dla każdego rekordu wejściowego.

Najwyraźniej moje umiejętności AWK są zardzewiałe! Dziękuję @chepner.

EDYCJA: i zobacz także jeszcze krótszą wersję od @Dennis Williamson:

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

Jak to działa, wyjaśniono w komentarzach: 1zawsze zwraca wartość true, więc powiązany blok kodu zawsze działa. Ale nie ma powiązanego bloku kodu, co oznacza, że ​​AWK wykonuje swoją domyślną akcję, po prostu wypisuje całą linię. AWK został zaprojektowany, aby zezwalać na takie zwięzłe programy.

steveha
źródło
3
Nieco krótsza: awk 'NR==4 {$0="different"} { print }' input_file.txt.
chepner
2
@chepner: Trochę krócej:awk 'NR==4 {$0="different"}1' input_file.txt
Wstrzymano do odwołania.
@DennisWilliamson, nawet nie wiem, jak to działa! Co robi to zakończenie 1? (Uwaga: przetestowałem to i rzeczywiście działa! Po prostu nie rozumiem jak.)
steveha
1
Pomyślałem, że będzie sposób na skrócenie bezwarunkowego druku! @stevaha: 1 to po prostu prawdziwa wartość, co oznacza „wzorzec”, który zawsze pasuje. Domyślną akcją dla dopasowania jest wydrukowanie bieżącego wiersza.
chepner
19

Biorąc pod uwagę ten plik testowy (test.txt)

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

poniższe polecenie zamieni pierwszą linię na „tekst nowego wiersza”

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

Wynik:

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

więcej informacji można znaleźć tutaj

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

Eric Robinson
źródło
2
To powinna być prawidłowa odpowiedź. Flaga c robi dokładnie to, co jest wymagane (zastępuje numerowaną linię podanym tekstem).
adelphus
Alternatywna składnia z sedem, która nie odzwierciedla pliku na stdout: stackoverflow.com/a/13438118/4669135
Gabriel Devillers
To właściwa odpowiedź, ale dlaczego brzydka przerwa w linii? sed '1 cnewline text' test.txtzrobiłby to również.
dev-null
7

w bash zamień N, M na numery linii, a xxx yyy na to, co chcesz

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

EDYTOWAĆ

W rzeczywistości w tym rozwiązaniu występują problemy ze znakami „\ 0” „\ t” i „\”

"\ t", można rozwiązać, umieszczając IFS = przed przeczytaniem: "\" na końcu linii z -r

IFS= read -r line

ale dla "\ 0" zmienna jest obcięta, nie ma rozwiązania w czystym bash: Przypisz ciąg zawierający znak null (\ 0) do zmiennej w Bash Ale w normalnym pliku tekstowym nie ma znaku nul \ 0

Perl byłby lepszym wyborem

perl -ne 'if($.==N){print"xxx\n"}elsif($.==M){print"yyy\n"}else{print}' < orig-file > new-file
Nahuel Fouilleul
źródło
Nawet nie pomyślałem o wypróbowaniu czystego rozwiązania BASH!
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
źródło
3

Doskonała odpowiedź od Chepnera. Działa dla mnie w 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`

tutaj
index- Line no
newLine- nowy ciąg linii, który chcemy zamienić.


Podobnie poniższy kod służy do odczytu konkretnej linii w pliku. Nie wpłynie to na rzeczywisty plik.

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

tutaj
!d- usunie linie inne niż linia nr. $index Otrzymamy wynik jako ciąg linii o numerze nie $indexw pliku.

Kanagavelu Sugumar
źródło
ale to mój bash, dlaczego wynik pokazuje się tylko ${newline}?
sikisis
1

Na komputerze Mac, którego użyłem

sed -i '' -e 's/text-on-line-to-be-changed.*/text-to-replace-the=whole-line/' file-name
nakeer
źródło
-2

Możesz nawet przekazać parametry do polecenia 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

Wykonanie ./test.sh daje nowy output.dat

a
b
c
replaced by '1'!
replaced by '2'!
replaced by '3'!
replaced by '4'!
replaced by '5'!
i
j
AndreH
źródło
1
dlaczego potrzebujesz pętli w tym przykładzie, po prostu zaciemniasz rozwiązanie? line=5; sed -i "${line}s/.*/replaced by '$i'!/" output.dat
Rob
Nie potrzebujesz do tego pętli
chandu vutukuri