Jak połączyć wszystkie linie zakończone znakiem odwrotnego ukośnika?

36

Czy za pomocą zwykłego narzędzia wiersza poleceń, takiego jak sed lub awk, można łączyć wszystkie linie kończące się danym znakiem, np. Odwrotny ukośnik?

Na przykład, biorąc pod uwagę plik:

foo bar \
bash \
baz
dude \
happy

Chciałbym uzyskać ten wynik:

foo bar bash baz
dude happy
Cory Klein
źródło
1
Przekaż plik cpp:)
imz - Ivan Zakharyaschev
Tyle wspaniałych odpowiedzi, chciałbym zaznaczyć je wszystkie jako odpowiedź! Dzięki za świetne spojrzenie na awk, sed i perl, były to świetne przykłady.
Cory Klein
Pamiętaj, że jest to w sedFAQ
Stéphane Chazelas

Odpowiedzi:

27

krótsze i prostsze rozwiązanie sed:

sed  '
: again
/\\$/ {
    N
    s/\\\n//
    t again
}
' textfile

lub jednowierszowy, jeśli używasz GNU sed:

sed ':x; /\\$/ { N; s/\\\n//; tx }' textfile
neurino
źródło
1
dobry ... Początkowo na to patrzyłem i nie mogłem tego zrozumieć (więc nie będzie to zbyt trudny koszyk) ... ale po dogłębnym spojrzeniu na odpowiedź Gillesa (która zajęła dość dużo czasu) Ponownie spojrzałem na twoją odpowiedź i wyglądało to niezwykle zrozumiale. Myślę, że zaczynam rozumieć sed:) ... Dołączasz każdą linię bezpośrednio do przestrzeni wzorów, a kiedy pojawia się linia „normalnie zakończona”, cała przestrzeń wzorów spada i auto drukuje (ponieważ nie ma opcji -n) ... fajnie! .. +1
Peter.O
@fred: dzięki Myślę, że też zaczynam rozumieć sed, oferuje ładne narzędzia do edycji wielowierszowej, ale sposób ich pomieszania, aby uzyskać to, czego potrzebujesz, nie jest prosty, a czytelność nie jest na szczycie ...
neurino
Uważaj na zakończenia linii DOS, alias. karetka zwraca lub \ r!
user77376,
1
Co jest nie tak zsed -e :a -e '/\\$/N; s/\\\n//; ta'
Izaakiem
18

Jest to prawdopodobnie najłatwiejsze w Perlu (ponieważ perl jest podobny do sed i awk, mam nadzieję, że jest dla ciebie akceptowalny):

perl -p -e 's/\\\n//'
camh
źródło
krótki i prosty, podoba mi się ten +1 I nie poprosił wprost o sed ani awk
rudolfson
2

To nie jest odpowiedź jako taka. Jest to kwestia poboczna sed.

W szczególności musiałem sedrozdzielić polecenie Gillesa kawałek po kawałku, aby to zrozumieć ... Zacząłem pisać na ten temat kilka uwag, a potem pomyślałem, że może się tu przydać komuś ...

więc oto jest ... skrypt Gillesa sed w udokumentowanym formacie:


#!/bin/bash
#######################################
sed_dat="$HOME/ztest.dat"
while IFS= read -r line ;do echo "$line" ;done <<'END_DAT' >"$sed_dat"
foo bar \
bash \
baz
dude \
happy
yabba dabba 
doo
END_DAT

#######################################
sedexec="$HOME/ztest.sed"
while IFS= read -r line ;do echo "$line" ;done <<'END-SED' >"$sedexec"; \
sed  -nf "$sedexec" "$sed_dat"

  s/\\$//        # If a line has trailing '\', remove the '\'
                 #    
  t'Hold-append' # branch: Branch conditionally to the label 'Hold-append'
                 #         The condition is that a replacement was made.
                 #         The current pattern-space had a trailing '\' which  
                 #         was replaced, so branch to 'Hold-apend' and append 
                 #         the now-truncated line to the hold-space
                 #
                 # This branching occurs for each (successive) such line. 
                 #
                 # PS. The 't' command may be so named because it means 'on true' 
                 #     (I'm not sure about this, but the shoe fits)  
                 #
                 # Note: Appending to the hold-space introduces a leading '\n'   
                 #       delimiter for each appended line
                 #  
                 #   eg. compare the hex dump of the follow 4 example commands:  
                 #       'x' swaps the hold and patten spaces
                 #
                 #       echo -n "a" |sed -ne         'p' |xxd -p  ## 61 
                 #       echo -n "a" |sed -ne     'H;x;p' |xxd -p  ## 0a61
                 #       echo -n "a" |sed -ne   'H;H;x;p' |xxd -p  ## 0a610a61
                 #       echo -n "a" |sed -ne 'H;H;H;x;p' |xxd -p  ## 0a610a610a61

   # No replacement was made above, so the current pattern-space
   #   (input line) has a "normal" ending.

   x             # Swap the pattern-space (the just-read "normal" line)
                 #   with the hold-space. The hold-space holds the accumulation
                 #   of appended  "stripped-of-backslah" lines

   G             # The pattern-space now holds zero to many "stripped-of-backslah" lines
                 #   each of which has a preceding '\n'
                 # The 'G' command Gets the Hold-space and appends it to 
                 #   the pattern-space. This append action introduces another
                 #   '\n' delimiter to the pattern space. 

   s/\n//g       # Remove all '\n' newlines from the pattern-space

   p             # Print the pattern-space

   s/.*//        # Now we need to remove all data from the pattern-space
                 # This is done as a means to remove data from the hold-space 
                 #  (there is no way to directly remove data from the hold-space)

   x             # Swap the no-data pattern space with the hold-space
                 # This leaves the hold-space re-initialized to empty...
                 # The current pattern-space will be overwritten by the next line-read

   b             # Everything is ready for the next line-read. It is time to make 
                 # an unconditional branch  the to end of process for this line
                 #  ie. skip any remaining logic, read the next line and start the process again.

  :'Hold-append' # The ':' (colon) indicates a label.. 
                 # A label is the target of the 2 branch commands, 'b' and 't'
                 # A label can be a single letter (it is often 'a')
                 # Note;  'b' can be used without a label as seen in the previous command 

    H            # Append the pattern to the hold buffer
                 # The pattern is prefixed with a '\n' before it is appended

END-SED
#######
Peter.O
źródło
1
Rozwiązanie Neurino jest właściwie dość proste. Mówiąc o lekko skomplikowanym sed, może cię to zainteresować .
Gilles „SO- przestań być zły”
2

Jeszcze innym powszechnym narzędziem wiersza poleceń byłoby ed, które domyślnie modyfikuje pliki w miejscu, a zatem pozostawia uprawnienia do plików niezmodyfikowane (więcej informacji na ten temat można edznaleźć w rozdziale Edytowanie plików za pomocą edytora tekstu ed ze skryptów )

str='
foo bar \
bash 1 \
bash 2 \
bash 3 \
bash 4 \
baz
dude \
happy
xxx
vvv 1 \
vvv 2 \
CCC
'

# We are using (1,$)g/re/command-list and (.,.+1)j to join lines ending with a '\'
# ?? repeats the last regex search.
# replace ',p' with 'wq' to edit files in-place
# (using Bash and FreeBSD ed on Mac OS X)
cat <<-'EOF' | ed -s <(printf '%s' "$str")
H
,g/\\$/s///\
.,.+1j\
??s///\
.,.+1j
,p
EOF
verdo
źródło
2

Wykorzystując fakt, że readw powłoce zinterpretuje odwrotne ukośniki, gdy jest używany bez -r:

$ while IFS= read line; do printf '%s\n' "$line"; done <file
foo bar bash baz
dude happy

Pamiętaj, że spowoduje to również interpretację wszelkich innych ukośników odwrotnych w danych.

Kusalananda
źródło
Nie. Nie usunie wszystkich odwrotnych ukośników. Spróbuj za\\b\\\\\\\\\\\c
Isaac
@Isaac Ach, może powinienem był powiedzieć „zinterpretować dowolny odwrotny ukośnik”?
Kusalananda
1

Proste (r) rozwiązanie, które ładuje cały plik do pamięci:

sed -z 's/\\\n//g' file                   # GNU sed 4.2.2+.

Lub jeszcze krótki, który działa rozumiejąc (wyjściowe) linie (składnia GNU):

sed ':x;/\\$/{N;bx};s/\\\n//g' file

W jednym wierszu (składnia POSIX):

sed -e :x -e '/\\$/{N;bx' -e '}' -e 's/\\\n//g' file

Lub użyj awk (jeśli plik jest zbyt duży, aby zmieścić się w pamięci):

awk '{a=sub(/\\$/,"");printf("%s%s",$0,a?"":RS)}' file
Izaak
źródło
0

Wersja Mac oparta na rozwiązaniu @Giles wyglądałaby tak

sed ':x
/\\$/{N; s|\\'$'\\n||; tx
}' textfile

Główna różnica polega na tym, jak reprezentowane są nowe linie, a łączenie ich w jedną linię powoduje jej przerwanie

Andy
źródło
-1

Możesz użyć cpp, ale tworzy kilka pustych wierszy, w których scalał dane wyjściowe, i pewne wprowadzenie, które usuwam za pomocą sed - być może można to również zrobić za pomocą flag cpp i opcji:

echo 'foo bar \
bash \
baz
dude \
happy' | cpp | sed 's/# 1 .*//;/^$/d'
foo bar bash baz
dude happy
nieznany użytkownik
źródło
Czy na pewno cpp jest to rozwiązanie? W twoim przykładzie echociąg z podwójnymi cudzysłowami już wyświetla wyprostowany tekst, więc nie cppma sensu. (Dotyczy to również twojegosed kodu.) Jeśli wstawisz ciąg w cudzysłowie, cpppo prostu usuwa ukośniki odwrotne, ale nie łączy wierszy. (Konkatenacja z cppdziałałaby, gdyby nie było miejsca przed odwrotnym ukośnikiem, ale wtedy osobne słowa byłyby łączone bez separatorów.)
manatwork
@manatwork: Outsch! :) Byłem zaskoczony, że polecenie sed działało, ale oczywiście nie było to polecenie sed, ale samo bash interpretuje łamanie linii odwrotnej jako kontynuację poprzedniej linii.
użytkownik nieznany
Używanie w cppten sposób wciąż nie łączy ze sobą wierszy. A użycie sedjest zdecydowanie niepotrzebne. Użyj cpp -P: „-P Hamuj generowanie znaczników linii w danych wyjściowych z preprocesora.” - man cpp
manatwork
Twoje polecenie nie działa dla mnie: cpp: “-P: No such file or directory cpp: warning: '-x c' after last input file has no effect cpp: unrecognized option '-P:' cpp: no input filescpp --version ujawnia cpp (Ubuntu 4.4.3-4ubuntu5.1) 4.4.3- co? Ubuntu łata cpp? Czemu? Spodziewałbym się przeczytać GNU ...
użytkownik nieznany
Ciekawy. Ubuntu cpprzeczywiście łączy linie i pozostawia pewne spacje. Jeszcze bardziej interesujące, ta sama wersja 4.4.3-4ubuntu5.1 tutaj akceptuje -P. Jednak tylko eliminuje znaczniki linii, puste linie pozostają.
manatwork