Come assegnare un valore di stringa a una variabile su più righe durante il rientro?


54

Il problema:

  1. Devo assegnare a una variabile un valore decentemente lungo.
  2. Tutte le righe della mia sceneggiatura devono essere sotto un certo numero di colonne.

Quindi, sto cercando di assegnarlo usando più di una riga.

È semplice fare a meno dei rientri:

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

Risultato:

This displays without any issues.

Tuttavia con rientri:

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

Risultato:

This displays with      extra spaces.

Come posso assegnarlo elegantemente senza questi spazi?

Risposte:


31

Qui il problema è che stai circondando la variabile tra virgolette (""). Rimuovilo e le cose funzioneranno bene.

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

Produzione

 This displays with extra spaces.

Qui il problema è che la doppia virgoletta di una variabile conserva tutti i caratteri degli spazi bianchi. Questo può essere usato nel caso in cui tu ne abbia esplicitamente bisogno.

Per esempio,

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

stamperà

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

E rimuovendo le virgolette, è diverso

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

Qui il Bash rimuove gli spazi extra nel testo perché nel primo caso l'intero testo viene preso come un "singolo" argomento e preservando così gli spazi extra. Ma nel secondo caso il echocomando riceve il testo come 5 argomenti.

Anche la citazione di una variabile sarà utile durante il passaggio di argomenti ai comandi.

Nel comando seguente, echoottiene solo un singolo argomento come"Hello World"

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

Ma nel caso dello scenario seguente echoottengono due argomenti come HelloeWorld

$ variable="Hello World"
$ echo $variable

La maggior parte delle risposte ha funzionato bene, ma questa è stata la più semplice. Grazie!
Sman865,

12
@ Sman865 - ti prego, credimi quando ti dico che questa è in realtà quasi sicuramente la risposta più complicata offerta qui, ed è anche sbagliata sotto molti aspetti, specialmente nella sua dichiarazione di apertura. Qualsiasi problema relativo all'assegnazione di valore non può essere in alcun modo correlato alla sua successiva espansione - questo è solo all'indietro. Mi dispiace Kannan, ma questa risposta è sia sbagliata che sbagliata. L'espansione del campo $IFSè uno strumento potente e universale, ma il codice scritto in questo modo non può mai produrre risultati affidabili di alcun tipo.
Mikeserv,

2
Il mancato utilizzo delle virgolette durante l'espansione di una variabile provoca problemi prima o poi, a partire dall'espansione del nome file (una delle * ? []).
mr.spuratic,

Sono d'accordo che le variabili devono essere racchiuse tra virgolette doppie per impedirne l'espansione bash. Ma per casi speciali possiamo evitarlo per prevenire complicazioni del codice.
Kannan Mohan,

1
Sono d'accordo con @mikeserv, questo è un pessimo consiglio se preso al valore nominale. Questo si romperà se utilizzato senza conseguenze. Ad esempio, se la variabile viene passata come argomento a un comando, non si desidera che ogni parola venga spezzata come argomento separato.
Haridsv,

28

Le soluzioni fornite da esuoxu e Mickaël Bucas sono i modi più comuni e portatili per farlo.

Ecco alcune bashsoluzioni (alcune delle quali dovrebbero funzionare anche in altre shell, come zsh). Innanzitutto con l' +=operatore append (che funziona in modo leggermente diverso per ciascuna di una variabile intera, una variabile regolare e un array).

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 ..." 

Se vuoi nuove righe (o altri spazi bianchi / escape) nel testo, usa $''invece le virgolette:

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

Quindi, utilizzare printf -vper assegnare un valore formattato a una variabile

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 ..."

Il trucco qui è che ci sono più argomenti che identificatori di formato, quindi a differenza della maggior parte delle printffunzioni, quello bash riutilizza la stringa di formato fino a quando non si esaurisce. Puoi inserire un \nall'interno della stringa di formato o utilizzare $ '', (o entrambi) per gestire gli spazi bianchi.

Quindi, utilizzando un array:

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 ..." )

Puoi anche usare +=per creare il testo riga per riga (nota ()):

text+=("post script")

Qui però, devi ricordare di "appiattire" l'array se vuoi l'intero contenuto del testo in una volta sola

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

(le matrici intere indicizzate sono ordinate in modo implicito, a differenza delle matrici associative) Ciò offre una flessibilità leggermente maggiore poiché è possibile manipolare linee e persino tagliare e tagliare, se necessario.

Infine, usando readoe readarrayun "documento qui":

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  

La forma del documento qui <<-significa che tutte le principali schede fisse vengono rimosse dall'input, quindi è necessario utilizzare le schede per indentare il testo. Le virgolette "EOT"impediscono le funzioni di espansione della shell, quindi l'input viene usato alla lettera. Con readesso utilizza l'input delimitato da byte NUL, in modo che legga il testo delimitato da nuova riga in una volta sola. Con readarray(aka mapfile, disponibile da bash-4.0), legge in un array e -tspoglia nuove righe su ogni riga.


1
L' readopzione con here documentè molto bella! Utile per incorporare script Python ed eseguire con python -c. script=$(cat <here document>)Prima lo facevo , ma read -r -d '' script <here document>è molto meglio.
Haridsv,

9

Esiste una sintassi heredoc speciale che rimuove le schede all'inizio di tutte le righe: "<< -" (notare il trattino aggiunto)

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

Esempio 19-4. Messaggio a più righe, con le schede eliminate

Puoi usarlo in questo modo:

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

Risultato:

A
B
C

Funziona solo con le schede, non con gli spazi.


Qui su Ubuntu è vero il contrario: gli spazi funzionano, non le schede. \tfunziona benissimo anche se hai bisogno di schede. Basta usare echo -e "$v"invece di echo "$v"abilitare i caratteri di barra rovesciata
will-ob,

5

Lascia che il guscio mangi gli avanzamenti di riga indesiderati e i seguenti spazi:

$ 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.

Quindi è possibile ... ma sicuramente è una questione di gusti piacere o non piacere a questa soluzione ...


3
ognuno di questi è una forchetta.
Mikeserv,

5

Forse puoi provare questo.

          echo "Test" \
               "Test2" \
               "Test3"

5

Ecco come ti suggerisco di farlo, e spiegherò perché, ma prima voglio parlare di qualcos'altro ...

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

Molte delle altre soluzioni offerte qui sembrano suggerire che puoi in qualche modo influenzare il contenuto di una variabile di shell alterando i tuoi metodi di espansione. Posso assicurarti che non è così.

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

PRODUZIONE

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

Quello che vedi sopra è prima un'espansione suddivisa in campi, quindi un rapporto sul conteggio dei byte per la variabile di origine dell'espansione, quindi un'espansione delimitata da virgolette e lo stesso conteggio dei byte. Mentre l'output può differire, il contenuto della variabile shell $stringnon cambia mai se non in caso di assegnazione.

Inoltre, se non capisci perché, sei costretto a incontrare brutte sorprese prima o poi. Riproviamo, ma in condizioni leggermente diverse.

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

Stesso $string- ambiente diverso.

PRODUZIONE

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

La suddivisione dei campi avviene in base ai delimitatori di campo definiti in $IFS. Esistono due tipi di delimitatori: $IFSspazi bianchi e $IFSqualsiasi altra cosa. Per impostazione predefinita $IFSviene assegnata la scheda spazio valore newline - che sono i tre possibili $IFSvalori degli spazi bianchi. Tuttavia, è facilmente modificabile, come puoi vedere sopra, e può avere effetti drastici sulle espansioni di divisione del campo.

$IFSgli spazi bianchi verranno elisi per sequenza in un singolo campo - e questo è il motivo echoper cui un'espansione contenente qualsiasi sequenza di spazi quando $IFScontiene uno spazio valuterà solo un singolo spazio - perché echoconcatena i suoi argomenti sugli spazi. Ma tutti i valori non di spazi bianchi non usciranno allo stesso modo e ogni delimitatore che si verifica ottiene sempre un campo a sé - come si può vedere nell'espansione delle cose sopra.

Questo non è il peggio. Considera questo altro $string.

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

PRODUZIONE

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

Sembra ok, vero? Bene, cambiamo di nuovo l'ambiente.

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

PRODUZIONE

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

Woah.

Per impostazione predefinita, la shell espanderà globs di nome file se può corrispondere a loro. Ciò si verifica dopo l' espansione dei parametri e la divisione dei campi nel suo ordine di analisi e quindi qualsiasi stringa non quotata è vulnerabile in questo modo. set -fSe lo desideri, puoi disattivare questo comportamento , ma qualsiasi shell compatibile con POSIX sarà sempre glob per impostazione predefinita.

Questo è il tipo di cose a cui ti trovi di fronte quando lasci cadere le virgolette sulle espansioni in base alle tue preferenze di rientro. E comunque, in ogni caso, indipendentemente dal suo comportamento di espansione, il valore effettivo $stringè sempre quello che era quando lo hai assegnato l'ultima volta. Quindi torniamo alla prima cosa.

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

PRODUZIONE

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

Credo che questo sia un modo molto più sano per adattare la sintassi della shell alle tue preferenze di rientro. Quello che sto facendo sopra è assegnare ogni singola stringa a un parametro posizionale - che può essere indicato da un numero come $1o ${33}- e quindi assegnare i loro valori concatenati $varall'utilizzo dello speciale parametro shell $*.

Questo approccio non è immune $IFS, anche così. Tuttavia, considero la sua relazione $IFSun ulteriore vantaggio in questo senso. Tener conto di:

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

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

PRODUZIONE

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.

Come puoi vedere, $*concatena ogni argomento nel "$@"primo byte in $IFS. Pertanto, salvando il suo valore mentre $IFSè assegnato in modo diverso, si ottengono delimitatori di campo diversi per ciascun valore salvato. A proposito, quello che vedi sopra è il valore letterale per ogni variabile. Se non volessi assolutamente delimitatore, faresti:

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

PRODUZIONE

Arg 1: Line 1.Arg 2: Line 2.and so on foras long as you might like. 67

2

Puoi provare:

echo $VAR | tr -s " "

o

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

e inoltre è possibile controllare questo fuori.



1

Questa è una variante per impostare variabili simili a percorsi:

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)

Utilizzando le setsovrascritture $@, che possono essere salvate e utilizzate in seguito come segue:

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

La LD_LIBRARY_PATH=$(sed 's/ :/:/g' <<< $LD_LIBRARY_PATH)linea elimina gli spazi prima dei due punti così come i possibili spazi bianchi finali. Se si eliminano solo gli spazi finali, utilizzare LD_LIBRARY_PATH=${LD_LIBRARY_PATH%% }invece.

L'intero approccio è una variante dell'ottima risposta di Mikeserv.


0

Non aver paura dei personaggi degli spazi bianchi. Basta rimuoverli prima di stampare il testo su più righe.

$ 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

Produzione:

$ ./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.

Non c'è bisogno di usare un <<-heredoc e rompere il rientro di 4 spazi con i caratteri di tabulazione.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.