Bash kontynuacja linii

158

Jak używasz linii kontynuacji basha?

Zdaję sobie sprawę, że możesz to zrobić:

echo "continuation \
lines"
>continuation lines

Jeśli jednak masz wcięty kod, to nie działa tak dobrze:

    echo "continuation \
    lines"
>continuation     lines
PyRulez
źródło
5
Przewodnik Google Bash Shell Style zaleca dokumenty „tutaj” dla „Jeśli musisz pisać ciągi dłuższe niż 80 znaków”. Zobacz odpowiedź @ tripleee .
Trevor Boyd Smith
google.github.io/styleguide/… - to jest bezpośredni link w nowym dokumencie
jcollum

Odpowiedzi:

161

To jest to, czego możesz chcieć

$       echo "continuation"\
>       "lines"
continuation lines

Jeśli tworzy to dwa argumenty do powtórzenia, a chcesz tylko jeden, spójrzmy na konkatenację ciągów. W bash, umieszczenie dwóch ciągów obok siebie, konkatenacja:

$ echo "continuation""lines"
continuationlines

Tak więc linia kontynuacji bez wcięcia jest jednym ze sposobów na podzielenie łańcucha:

$ echo "continuation"\
> "lines"
continuationlines

Ale kiedy używane jest wcięcie:

$       echo "continuation"\
>       "lines"
continuation lines

Otrzymujesz dwa argumenty, ponieważ nie jest to już konkatenacja.

Jeśli chciałbyś, aby pojedynczy ciąg, który przecinał linie, jednocześnie wciskając wszystkie te spacje, ale nie uzyskując wszystkich tych spacji, możesz spróbować porzucić linię kontynuacji i użyć zmiennych:

$ a="continuation"
$ b="lines"
$ echo $a$b
continuationlines

Umożliwi to uzyskanie czystego wcięcia kodu kosztem dodatkowych zmiennych. Jeśli zmienisz zmienne lokalnymi, nie powinno być tak źle.

Ray Toal
źródło
1
Dziękuję za pomoc, ale chociaż to usuwa spacje, są one teraz oddzielnymi parametrami (Bash interpretuje spacje w drugiej linii jako separator parametrów) i są teraz drukowane poprawnie tylko z powodu polecenia echo.
1
Och, chciałbyś, aby pojedynczy ciąg (bash) obejmował linie! Teraz widzę.
Ray Toal,
4
Rozwiązanie z jedną zmienną: s="string no. 1" s+="string no. 2" s+=" string no. 3" echo "$s"
Johnny Thunderman
30

Tutaj dokumenty z <<-HEREterminatorem działają dobrze w przypadku wciętych wieloliniowych ciągów tekstowych. Spowoduje to usunięcie wszystkich wiodących zakładek z tego dokumentu. (Terminatory linii pozostaną jednak nadal.)

cat <<-____HERE
    continuation
    lines
____HERE

Zobacz także http://ss64.com/bash/syntax-here.html

Jeśli chcesz zachować niektóre, ale nie wszystkie, początkowe spacje, możesz użyć czegoś takiego jak

sed 's/^  //' <<____HERE
    This has four leading spaces.
    Two of them will be removed by sed.
____HERE

a może użyj, traby pozbyć się nowych linii:

tr -d '\012' <<-____
    continuation
     lines
____

(Druga linia ma zakładkę i spację z przodu; tabulator zostanie usunięty przez operatora myślnika przed terminatorem heredoc, podczas gdy spacja zostanie zachowana.)

Do owijania długich, złożonych ciągów w wielu liniach lubię printf:

printf '%s' \
    "This will all be printed on a " \
    "single line (because the format string " \
    "doesn't specify any newline)"

Działa również dobrze w kontekstach, w których chcesz osadzić nietrywialne fragmenty skryptu powłoki w innym języku, w którym składnia języka hosta nie pozwala na użycie dokumentu tutaj, na przykład w Makefilelub Dockerfile.

printf '%s\n' >./myscript \
    '#!/bin/sh` \
    "echo \"G'day, World\"" \
    'date +%F\ %T' && \
chmod a+x ./myscript && \
./myscript
tripleee
źródło
Nie działa na mnie. Ubuntu 16.04. Otrzymuję dwa wiersze zamiast oczekiwanego połączonego wiersza.
Penghe Geng,
@PengheGeng Rzeczywiście, rozwiązuje to problem pozbycia się wcięć, a nie łączenia linii. Nadal możesz użyć odwrotnego ukośnika na końcu linii, aby połączyć ze sobą dwie linie.
tripleee
(Ale zobacz też pierwszy printfprzykład teraz.)
tripleee
12

Możesz używać tablic bash

$ str_array=("continuation"
             "lines")

następnie

$ echo "${str_array[*]}"
continuation lines

jest dodatkowa spacja, ponieważ (po instrukcji bash):

Jeśli słowo jest umieszczone w cudzysłowie, ${name[*]}rozwija się do pojedynczego słowa z wartością każdego elementu tablicy oddzieloną pierwszym znakiem zmiennej IFS

Więc ustaw, IFS=''aby pozbyć się dodatkowej przestrzeni

$ IFS=''
$ echo "${str_array[*]}"
continuationlines
tworec
źródło
4

W niektórych scenariuszach użycie zdolności konkatenacji Bash może być odpowiednie.

Przykład:

temp='this string is very long '
temp+='so I will separate it onto multiple lines'
echo $temp
this string is very long so I will separate it onto multiple lines

Z sekcji PARAMETRY strony Bash Man:

name = [wartość] ...

... W kontekście, w którym instrukcja przypisania przypisuje wartość do zmiennej powłoki lub indeksu tablicy, operator + = może być użyty do dołączenia lub dodania do poprzedniej wartości zmiennej. Gdy + = zostanie zastosowane do zmiennej, dla której ustawiono atrybut liczby całkowitej, wartość jest oceniana jako wyrażenie arytmetyczne i dodawana do bieżącej wartości zmiennej, która również jest oceniana. Gdy + = zostanie zastosowane do zmiennej tablicowej przy użyciu przypisania złożonego (zobacz Tablice poniżej), wartość zmiennej nie jest nieustawiona (tak jak to ma miejsce w przypadku użycia =), a nowe wartości są dołączane do tablicy, zaczynając o jeden większy niż maksymalny indeks tablicy (dla tablic indeksowanych) lub dodane jako dodatkowe pary klucz-wartość w tablicy asocjacyjnej. Po zastosowaniu do zmiennej o wartości łańcuchowej wartość jest rozwijana i dołączana do wartości zmiennej.

Cybernaut
źródło
Prawdopodobnie najlepsza rada na tej stronie. Używanie HEREDOC i potokowanie do translacji dla <CR> s jest bardzo nieintuicyjne i kończy się niepowodzeniem, gdy łańcuch faktycznie wymaga dyskretnie umieszczonych separatorów linii.
ingyhere
2

Natknąłem się na sytuację, w której musiałem wysłać długą wiadomość jako część argumentu polecenia i musiałem przestrzegać ograniczenia długości linii. Polecenia wyglądają mniej więcej tak:

somecommand --message="I am a long message" args

Sposób, w jaki to rozwiązałem, to przeniesienie wiadomości jako dokumentu tutaj (jak sugerował @tripleee). Ale tutaj dokument staje się standardem, więc należy go ponownie przeczytać, poszedłem z poniższym podejściem:

message=$(
    tr "\n" " " <<- END
        This is a
        long message
END
)
somecommand --message="$message" args

Ma to tę zaletę, że $messagemoże być użyte dokładnie jako stała łańcuchowa bez dodatkowych białych znaków lub znaków końca wiersza.

Zauważ, że wszystkie powyższe wiersze wiadomości są poprzedzone znakiem tab, który jest usuwany w tym dokumencie (z powodu użycia <<-). Na końcu nadal znajdują się podziały wierszy, które są następnie zastępowane ddspacjami.

Zauważ również, że jeśli nie usuniesz nowych linii, będą one wyglądały tak, jak "$message"są rozwinięte. W niektórych przypadkach możesz obejść ten problem, usuwając podwójne cudzysłowy $message, ale wiadomość nie będzie już pojedynczym argumentem.

haridsv
źródło
2

Kontynuację linii można również osiągnąć poprzez sprytne użycie składni.

W przypadku echo:

# echo '-n' flag prevents trailing <CR> 
echo -n "This is my one-line statement" ;
echo -n " that I would like to make."
This is my one-line statement that I would like to make.

W przypadku vars:

outp="This is my one-line statement" ; 
outp+=" that I would like to make." ; 
echo -n "${outp}"
This is my one-line statement that I would like to make.

Inne podejście w przypadku vars:

outp="This is my one-line statement" ; 
outp="${outp} that I would like to make." ; 
echo -n "${outp}"
This is my one-line statement that I would like to make.

Voila!

ingyhere
źródło
1

Możesz po prostu oddzielić go znakami nowej linii (bez używania odwrotnego ukośnika) zgodnie z wymaganiami w ramach wcięcia w następujący sposób i po prostu usunąć nowe linie.

Przykład:

echo "continuation
of 
lines" | tr '\n' ' '

Lub jeśli jest to zmienna definicja nowej linii jest automatycznie konwertowana na spacje. Tak więc, pasek dodatkowych spacji tylko jeśli ma to zastosowanie.

x="continuation
of multiple
lines"
y="red|blue|
green|yellow"

echo $x # This will do as the converted space actually is meaningful
echo $y | tr -d ' ' # Stripping of space may be preferable in this case
rjni
źródło
1

Nie jest to dokładnie to, o co prosił użytkownik, ale innym sposobem na utworzenie długiego ciągu, który obejmuje wiele wierszy, jest jego przyrostowe tworzenie, na przykład:

$ greeting="Hello"
$ greeting="$greeting, World"
$ echo $greeting
Hello, World

Oczywiście w tym przypadku byłoby łatwiej zbudować go za jednym zamachem, ale ten styl może być bardzo lekki i zrozumiały, gdy mamy do czynienia z dłuższymi strunami.

Jon McClung
źródło
0

Jeśli jednak masz wcięty kod, to nie działa tak dobrze:

    echo "continuation \
    lines"
>continuation     lines

Spróbuj z pojedynczymi cudzysłowami i połącz łańcuchy:

    echo 'continuation' \
    'lines'
>continuation lines

Uwaga: konkatenacja zawiera spacje.

mMontu
źródło
2
Działa z argumentami echo i stringami, ale nie działa z innymi rzeczami, takimi jak przypisywanie zmiennych. Chociaż pytanie nie dotyczyło zmiennych, użycie echa było tylko przykładem. Zamiast echo jeśli miał x=, to pojawi się błąd: lines: command not found.
LS
0

W zależności od tego, jakie rodzaje ryzyka zaakceptujesz oraz jak dobrze znasz dane i im ufasz, możesz użyć uproszczonej interpolacji zmiennych.

$: x="
    this
    is
       variably indented
    stuff
   "
$: echo "$x" # preserves the newlines and spacing

    this
    is
       variably indented
    stuff

$: echo $x # no quotes, stacks it "neatly" with minimal spacing
this is variably indented stuff
Paul Hodges
źródło
-2

To prawdopodobnie nie jest odpowiedzią na Twoje pytanie, ale i tak może Ci się przydać.

Pierwsze polecenie tworzy skrypt, który jest wyświetlany przez drugie polecenie.

Trzecie polecenie sprawia, że ​​skrypt jest wykonywalny.

Czwarte polecenie zawiera przykład użycia.

john@malkovich:~/tmp/so$ echo $'#!/usr/bin/env python\nimport textwrap, sys\n\ndef bash_dedent(text):\n    """Dedent all but the first line in the passed `text`."""\n    try:\n        first, rest = text.split("\\n", 1)\n        return "\\n".join([first, textwrap.dedent(rest)])\n    except ValueError:\n        return text  # single-line string\n\nprint bash_dedent(sys.argv[1])'  > bash_dedent
john@malkovich:~/tmp/so$ cat bash_dedent 
#!/usr/bin/env python
import textwrap, sys

def bash_dedent(text):
    """Dedent all but the first line in the passed `text`."""
    try:
        first, rest = text.split("\n", 1)
        return "\n".join([first, textwrap.dedent(rest)])
    except ValueError:
        return text  # single-line string

print bash_dedent(sys.argv[1])
john@malkovich:~/tmp/so$ chmod a+x bash_dedent
john@malkovich:~/tmp/so$ echo "$(./bash_dedent "first line
>     second line
>     third line")"
first line
second line
third line

Zwróć uwagę, że jeśli naprawdę chcesz użyć tego skryptu, bardziej sensowne jest przeniesienie wykonywalnego skryptu do programu ~/bin, aby znalazł się na Twojej ścieżce.

Sprawdź dokumentację Pythona, aby uzyskać szczegółowe informacje o tym, jak textwrap.dedentdziała.

Jeśli użycie $'...'lub "$(...)"jest dla Ciebie mylące, zadaj kolejne pytanie (jedno na konstrukcję), jeśli jeszcze takiego nie ma. Dobrze byłoby podać link do znalezionego / zadanego pytania, aby inne osoby miały odnośnik.

intuicyjnie
źródło
6
Mając dobre intencje - i być może nawet przydatne - chociaż może to być, OP poprosił o radę na temat podstawowej składni basha, a ty dałeś mu definicję funkcji Pythona, która używa paradygmatów OO, wyjątkowej kontroli przepływu i importu. Co więcej, wywołałeś plik wykonywalny jako część interpolacji ciągów - coś, czego osoba zadająca tego rodzaju pytanie z pewnością nie widziałaby jeszcze w bashu.
Parthian Shot