Jak przypisać wartość ciągu zmiennej do wielu wierszy podczas wcięcia?

54

Problem:

  1. Muszę przypisać zmiennej dość przyzwoitą wartość.
  2. Wszystkie wiersze mojego skryptu muszą znajdować się pod określoną liczbą kolumn.

Więc próbuję przypisać to przy użyciu więcej niż jednej linii.

Łatwo jest obejść się bez wcięć:

VAR="This displays without \
any issues."
echo "${VAR}"

Wynik:

This displays without any issues.

Jednak z wcięciami:

    VAR="This displays with \
    extra spaces."
    echo "${VAR}"

Wynik:

This displays with      extra spaces.

Jak mogę to elegancko przypisać bez tych przestrzeni?

Sman865
źródło

Odpowiedzi:

30

Problem polega na tym, że otaczasz zmienną podwójnymi cudzysłowami („”). Usuń go, a wszystko będzie działało dobrze.

    VAR="This displays with \
    extra spaces."
    echo ${VAR}

Wynik

 This displays with extra spaces.

Problem polega na tym, że podwójne cytowanie zmiennej zachowuje wszystkie znaki białych znaków. Można tego użyć, jeśli jest to wyraźnie potrzebne.

Na przykład,

$ echo "Hello     World    ........ ...            ...."

wydrukuje

Hello     World    ........ ...            ....

A po usunięciu cudzysłowów jest inaczej

$ echo Hello     World    ........ ...            ....
Hello World ........ ... ....

Tutaj Bash usuwa dodatkowe spacje w tekście, ponieważ w pierwszym przypadku cały tekst jest traktowany jako „pojedynczy” argument, a tym samym zachowuje dodatkowe spacje. Ale w drugim przypadku echopolecenie otrzymuje tekst jako 5 argumentów.

Cytowanie zmiennej będzie również pomocne przy przekazywaniu argumentów do poleceń.

W poniższym poleceniu echopobiera tylko jeden argument jako"Hello World"

$ variable="Hello World"
$ echo "$variable"

Ale w przypadku poniższego scenariusza echodostaje dwa argumenty jako HelloiWorld

$ variable="Hello World"
$ echo $variable
Kannan Mohan
źródło
Większość odpowiedzi działała dobrze, ale była najprostsza. Dzięki!
Sman865
12
@ Sman865 - proszę, uwierz mi, gdy powiem ci, że jest to prawie zdecydowanie najbardziej skomplikowana odpowiedź tutaj oferowana, a także pod wieloma względami jest błędna - szczególnie w deklaracji otwierającej. Jakikolwiek problem związany z przypisywaniem wartości nie może być w żaden sposób związany z jego późniejszym rozszerzeniem - to tylko wstecz. Przykro mi Kannan, ale ta odpowiedź jest zarówno błędna, jak i błędna. Rozszerzenia pola dzielące $IFSsą potężnym i uniwersalnym narzędziem - ale kod napisany w ten sposób nigdy nie może dać żadnych wiarygodnych wyników.
mikeserv
2
Nieużywanie cudzysłowów podczas rozwijania zmiennej powoduje prędzej czy później problemy, zaczynając od rozszerzenia nazwy pliku (dowolnego z * ? []).
mr.spuratic
Zgadzam się, że zmienne muszą być zawarte w podwójnych cudzysłowach, aby zapobiec ich rozszerzeniu. Ale w szczególnych przypadkach możemy tego uniknąć, aby uniknąć komplikacji kodu.
Kannan Mohan
1
Zgadzam się z @mikeserv, jest to bardzo zła rada, jeśli zostanie przyjęta za dobrą monetę. To pęknie, jeśli zostanie użyte bez konsekwencji. Na przykład, jeśli zmienna jest przekazywana jako argument do polecenia, nie chcesz, aby każde słowo było dzielone jako osobny argument.
haridsv
28

Rozwiązania podane przez esuoxu i Mickaël Bucas są powszechnymi i bardziej przenośnymi sposobami na to.

Oto kilka bashrozwiązań (niektóre z nich powinny również działać w innych powłokach, takich jak zsh). Po pierwsze za pomocą +=operatora dołączającego (który działa w nieco inny sposób dla każdej zmiennej całkowitej, zmiennej zwykłej i tablicy).

text="Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod "
text+="tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, "
text+="quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea ..." 

Jeśli chcesz wstawić nowe wiersze (lub inne białe znaki / znaki specjalne) w tekście, $''zamiast tego użyj cudzysłowu:

text=$'Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod\n'
text+=$'...'

Następnie za pomocą printf -vprzypisać sformatowaną wartość zmiennej

printf -v text "%s" "Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed " \
                    "do eiusmod empor incididunt ut labore et dolore magna aliqua. "\
                    "Ut enim ad minim veniam ..."

Sztuczka polega na tym, że argumentów jest więcej niż specyfikatorów formatu, więc w przeciwieństwie do większości printffunkcji, bash używa łańcucha formatu, dopóki się nie skończy. Możesz wpisać \nciąg formatu lub użyć $ '' (lub obu), aby zająć się spacjami.

Następnie za pomocą tablicy:

text=("Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod "
      "tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, "
      "quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea ..." )

Możesz także użyć +=do zbudowania tekstu wiersz po wierszu (zwróć uwagę na ()):

text+=("post script")

Tutaj jednak musisz pamiętać o „spłaszczeniu” tablicy, jeśli chcesz mieć całą zawartość tekstu za jednym razem

echo "$text"      # only outputs index [0], the first line
echo "${text[*]}" # output complete text (joined by first character of IFS)

(tablice indeksowane liczbami całkowitymi są domyślnie sortowane, w przeciwieństwie do tablic asocjacyjnych) Daje to nieco większą elastyczność, ponieważ można manipulować liniami, a nawet kroić i kroić w razie potrzeby.

Wreszcie za pomocą readlub readarrayi „tutaj-dokumentu”:

read -r -d '' text <<-"EOT"
        Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod
        tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, 
        quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea ...
EOT

readarray -t textarray <<-"EOT"
        Lorem [...]
EOT  

Forma dokumentu tutaj <<-oznacza, że ​​wszystkie wiodące twarde tabulatory są usuwane z danych wejściowych, więc musisz używać tabulatorów, aby wciąć tekst. Cudzysłowy "EOT"uniemożliwiają ekspansję powłoki, więc dane wejściowe są używane dosłownie. Dzięki readtemu korzysta z danych rozdzielanych bajtami NUL, dzięki czemu odczytuje tekst rozdzielany znakiem nowej linii za jednym razem. Za pomocą readarray(znanego mapfilerównież od wersji bash-4.0) wczytuje tablicę i -tusuwa nowe linie w każdej linii.

pan. spuratic
źródło
1
readOpcja ze here documentjest bardzo miłe! Przydatne do osadzania skryptów Python i wykonywania ich za pomocą python -c. Robiłem to script=$(cat <here document>)wcześniej, ale read -r -d '' script <here document>jest znacznie lepsze.
haridsv
9

Istnieje specjalna składnia heredoc, która usuwa tabulatory na początku wszystkich wierszy: „<< -” (zauważ dodaną kreskę)

http://tldp.org/LDP/abs/html/here-docs.html

Przykład 19-4. Wiadomość wieloliniowa, z pominiętymi zakładkami

Możesz użyć tego w następujący sposób:

v="$(cat <<-EOF
    A
        B
    C
EOF
)"
echo "$v"

Wynik:

A
B
C

Działa tylko z tabulatorami, a nie spacjami.

Mickaël Bucas
źródło
Tutaj w Ubuntu jest odwrotnie - spacje działają, a nie tabulatory. \tdziała świetnie, jeśli potrzebujesz kart. Po prostu użyj echo -e "$v"zamiast, echo "$v"aby włączyć znaki odwrotnego ukośnika
will-ob
5

Pozwól muszli zjadać niechciane pokarmy i następujące przestrzenie:

$ cat weird.sh 
#!/bin/sh

        var1="A weird(?) $(
             )multi line $(
             )text idea. $(
             )PID=$$"

        var2='You can '$(
            )'avoid expansion '$(
            )'too: PID=$$'

        var3='Or mix it: '$(
            )'To insert the PID use $$. '$(
            )"It expands to e.g. $$."

        echo "$var1"
        echo "$var2"
        echo "$var3"
$ sh weird.sh 
A weird(?) multi line text idea. PID=13960
You can avoid expansion too: PID=$$
Or mix it: To insert the PID use $$. It expands to e.g. 13960.

Jest więc możliwe ... ale pewne, że polubienie lub rozwiązanie tego rozwiązania jest kwestią gustu ...

yeti
źródło
3
każdy z nich to widelec.
mikeserv
5

Może możesz tego spróbować.

          echo "Test" \
               "Test2" \
               "Test3"
esuoxu
źródło
5

Tak sugeruję, abyś to zrobił, a ja wyjaśnię dlaczego, ale najpierw chcę porozmawiać o czymś innym ...

set -- 'Arg 1: Line 1.' \
       'Arg 2: Line 2.' \
       'and so on for'  \
       'as long as you might like.'
var="$*"

Wiele innych zaproponowanych tutaj rozwiązań wydaje się sugerować, że możesz w jakiś sposób wpłynąć na zawartość zmiennej powłoki, zmieniając metody jej rozszerzania. Zapewniam cię, że tak nie jest.

    string="some stuff here \
            some more stuff here."
    echo $string ${#string} 
    echo "$string" "${#string}"

WYNIK

some stuff here some more stuff here. 53
some stuff here                 some more stuff here. 53

To, co widzisz powyżej, to najpierw rozwinięcie podzielone na pola, następnie raport o liczbie bajtów zmiennej źródłowej rozszerzenia, następnie rozszerzenie rozdzielane cudzysłowem i ta sama liczba bajtów. Chociaż dane wyjściowe mogą się różnić, zawartość zmiennej powłoki $stringnigdy się nie zmienia, z wyjątkiem przypisania.

Co więcej, jeśli nie rozumiesz, dlaczego tak się dzieje, na pewno spotka Cię kilka nieprzyjemnych niespodzianek wcześniej niż później. Spróbujmy jeszcze raz, ale w nieco innych warunkach.

    IFS=sf
    echo $string ${#string} 
    echo "$string" "${#string}"

To samo $string- inne środowisko.

WYNIK

 ome  tu   here                  ome more  tu   here. 53
some stuff here                 some more stuff here. 53

Podział pola następuje na podstawie ograniczników pól zdefiniowanych w $IFS. Istnieją dwa rodzaje ograniczników - $IFSbiałe znaki i $IFScokolwiek innego. Domyślnie $IFSprzypisana jest nowa linia tabulatora przestrzeni wartości - trzy możliwe $IFSwartości białych znaków. Można to łatwo zmienić, jak widać powyżej, i może mieć drastyczny wpływ na rozszerzenia podzielone na pola.

$IFSbiałe znaki będą się przemieszczały sekwencyjnie do pojedynczego pola - i dlatego echorozwinięcie zawierające dowolną sekwencję spacji, gdy $IFSzawiera spację, będzie miało wartość tylko do pojedynczej spacji - ponieważ echołączy argumenty spacji. Ale wartości inne niż spacja nie będą się zachowywać w ten sam sposób, a każdy występujący separator zawsze otrzymuje pole do siebie - jak widać w powyższym rozwinięciu rzeczy .

To nie jest najgorsze. Rozważ to inne $string.

IFS=$space$tab$newline
cd emptydir
    string=" * * * \
             * * * "
    echo $string ${#string}
    echo "$string" "${#string}"    

WYNIK

* * * * * * 30
 * * *                  * * *  30

Wygląda dobrze, prawda? Cóż, zmieńmy ponownie środowisko.

    touch file1 file2 file3 file4 file5
    echo $string ${#string}
    echo "$string" "${#string}"    

WYNIK

file1 file2 file3 file4 file5 file1 file2 file3 file4 file5 file1 file2 file3 file4 file5 file1 file2 file3 file4 file5 file1 file2 file3 file4 file5 file1 file2 file3 file4 file5 30
 * * *                  * * *  30

Łał

Domyślnie powłoka rozszerzy globs nazw plików, jeśli można je dopasować. Dzieje się tak po rozwinięciu parametru i podziale pola w kolejności parsowania, dlatego każdy niecytowany łańcuch jest narażony w ten sposób. Możesz wyłączyć to zachowanie, set -fjeśli chcesz, ale domyślnie każda powłoka zgodna z POSIX będzie zawsze globować.

To jest rodzaj rzeczy, z którymi masz do czynienia, gdy upuszczasz cudzysłowy na rozszerzeniach, aby pasowały do ​​twoich preferencji wcięcia. Mimo to, w każdym przypadku, niezależnie od zachowania podczas ekspansji, rzeczywista wartość dla $stringzawsze jest taka, jaka była podczas ostatniego przypisania. Wróćmy więc do pierwszej rzeczy.

set -- 'Arg 1: Line 1.' \
       'Arg 2: Line 2.' \
       'and so on for'  \
       'as long as you might like.'
var="$*"
echo "$var" "${#var}"

WYNIK

Arg 1: Line 1. Arg 2: Line 2. and so on for as long as you might like. 70

Uważam, że jest to znacznie rozsądniejszy sposób dostosowania składni powłoki do preferencji wcięcia. To, co robię powyżej, polega na przypisaniu każdego łańcucha do parametru pozycyjnego - do którego każdy może się odwoływać za pomocą liczby, takiej jak $1lub ${33}- a następnie przypisaniu ich połączonych wartości do $varużycia specjalnego parametru powłoki $*.

Takie podejście nie jest jednak odporne $IFS. Mimo to uważam jego związek z $IFSdodatkową korzyścią w tym zakresie. Rozważać:

IFS=\ ;space_split="$*"
IFS=/; slash_split="$*";IFS='
';new_line_split="$*"

echo "$space_split"
echo "$slash_split"
echo "$new_line_split"

WYNIK

Arg 1: Line 1. Arg 2: Line 2. and so on for as long as you might like.
Arg 1: Line 1./Arg 2: Line 2./and so on for/as long as you might like.
Arg 1: Line 1.
Arg 2: Line 2.
and so on for
as long as you might like.

Jak widać, $*konkatenuje każdy argument w "$@"pierwszym bajcie w $IFS. Zatem zapisanie jego wartości, gdy $IFSjest ona przypisana w inny sposób, powoduje otrzymanie różnych separatorów pól dla każdej zapisanej wartości. Nawiasem mówiąc, to, co widzisz powyżej, to dosłowna wartość każdej zmiennej. Jeśli w ogóle nie chcesz ogranicznika, zrób:

IFS=;delimitless="$*"
echo "$delimitless" "${#delimitless}"

WYNIK

Arg 1: Line 1.Arg 2: Line 2.and so on foras long as you might like. 67
mikeserv
źródło
2

Możesz spróbować:

echo $VAR | tr -s " "

lub

myArr=($VAL)
VAL=${myArr[@]}
echo "$VAL"

a także można sprawdzić to na zewnątrz.

Vivian Maya
źródło
2

Użyj rozszerzenia podstawienia Bash

Jeśli używasz Bash, możesz użyć rozszerzenia podstawiania . Na przykład:

$ echo "${VAR//  /}"
This displays with extra spaces.
CodeGnome
źródło
1

Jest to wariant ustawiania zmiennych podobnych do ścieżki:

set -- "${MYDIR}/usr/local/lib" \
      :"${MYDIR}/usr/lib" \
      :"${MYDIR}/lib" \
       "${LD_LIBRARY_PATH:+:$LD_LIBRARY_PATH}"
    export LD_LIBRARY_PATH="$*"
    LD_LIBRARY_PATH=$(sed 's/ :/:/g' <<< $LD_LIBRARY_PATH)

Używanie setzastępowania $@, które można zapisać i wykorzystać później w następujący sposób:

ARGV=("$@")
exec foo "${ARGV[@]}"

LD_LIBRARY_PATH=$(sed 's/ :/:/g' <<< $LD_LIBRARY_PATH)Linia eliminuje spacji przed dwukropkiem, a także możliwego spacji spływu. Jeśli tylko eliminujesz końcowe spacje, użyj LD_LIBRARY_PATH=${LD_LIBRARY_PATH%% }zamiast tego.

Całe to podejście jest wariantem doskonałej odpowiedzi mikeserv.

rsanden
źródło
0

Nie bój się białych znaków. Po prostu usuń je przed wydrukowaniem tekstu wielowierszowego.

$ cat ./t.sh
#!/bin/bash

NEED_HELP=1
if [[ $NEED_HELP -eq 1 ]]; then

    lucky_number=$((1 + RANDOM % 10 + 10))

    read -r -d '' text <<'    EOF'
    NAME says hello

    Look at this helpful text:

                                                 * -**
                                 Eyjafjallajokull        Eyjafjallajokull
                            Eyjafjallajokull Eyjafjallajokull Eyjafjallajokull
                        Eyjafjallajokull Eyjafjallajokull Eyjafjallajokull Eyjafjallajokull
                    Eyjafjallajokull Eyjafjallajokull Eyjafjallajokull Eyjafjallajokull
                Eyjafjallajokull Eyjafjallajokull Eyjafjallajokull Eyjafjallajokull Eyjafjallajokull
            Eyjafjallajokull Eyjafjallajokull Eyjafjallajokull Eyjafjallajokull Eyjafjallajokull Eyjafjallajokull
        Eyjafjallajokull Eyjafjallajokull Eyjafjallajokull Eyjafjallajokull Eyjafjallajokull Eyjafjallajokull
    Eyjafjallajokull Eyjafjallajokull Eyjafjallajokull Eyjafjallajokull Eyjafjallajokull Eyjafjallajokull Eyjafjallajokull

    Don't go away, please rate your experience... It'll just take two minutes.

    EOF
    text=$(echo "$text" | sed -r 's!^\s{4}!!')
    text=$(echo "$text" | sed -r "s!\bNAME\b!$0!") # Bash: text=${text//NAME/$0}
    text=$(echo "$text" | sed -r "s!\btwo\b!$lucky_number!")

    echo "$text"

fi

Wynik:

$ ./t.sh
./t.sh says hello

Look at this helpful text:

                                             * -**
                             Eyjafjallajokull        Eyjafjallajokull
                        Eyjafjallajokull Eyjafjallajokull Eyjafjallajokull
                    Eyjafjallajokull Eyjafjallajokull Eyjafjallajokull Eyjafjallajokull
                Eyjafjallajokull Eyjafjallajokull Eyjafjallajokull Eyjafjallajokull
            Eyjafjallajokull Eyjafjallajokull Eyjafjallajokull Eyjafjallajokull Eyjafjallajokull
        Eyjafjallajokull Eyjafjallajokull Eyjafjallajokull Eyjafjallajokull Eyjafjallajokull Eyjafjallajokull
    Eyjafjallajokull Eyjafjallajokull Eyjafjallajokull Eyjafjallajokull Eyjafjallajokull Eyjafjallajokull
Eyjafjallajokull Eyjafjallajokull Eyjafjallajokull Eyjafjallajokull Eyjafjallajokull Eyjafjallajokull Eyjafjallajokull

Don't go away, please rate your experience... It'll just take 16 minutes.

Nie ma potrzeby używania <<-heredoc i łamania 4-spacji wcięcia znakami tabulacji.

c0xc
źródło