Як призначити значення рядка змінній у кількох рядках з відступом?


54

Питання:

  1. Мені потрібно призначити змінній значення, яке є пристойно довгим.
  2. Усі рядки мого сценарію повинні знаходитися під певною кількістю стовпців.

Отже, я намагаюся призначити це за допомогою декількох рядків.

Це легко зробити без відступів:

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

Результат:

This displays without any issues.

Однак з відступами:

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

Результат:

This displays with      extra spaces.

Як я можу це елегантно призначити без цих пробілів?

Відповіді:


31

Тут питання полягає в тому, що ви оточуєте змінну подвійними лапками (""). Видаліть його, і все буде добре.

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

Вихід

 This displays with extra spaces.

Тут питання полягає в тому, що подвійне цитування змінної зберігає всі символи пробілу. Це можна використовувати в тому випадку, якщо вам явно це потрібно.

Наприклад,

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

буде надруковано

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

А щодо видалення цитат, її різні

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

Тут Bash видаляє зайві пробіли в тексті, оскільки в першому випадку весь текст приймається як "єдиний" аргумент і таким чином зберігається додаткові пробіли. Але у другому випадку echoкоманда отримує текст у вигляді 5 аргументів.

Цитування змінної також буде корисним при передачі аргументів командам.

У нижченаведеній команді echoотримується лише один аргумент як"Hello World"

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

Але у випадку нижченаведеного сценарію echoвиходить два аргументи як HelloіWorld

$ variable="Hello World"
$ echo $variable

Більшість відповідей спрацювали добре, але це було найпростіше. Дякую!
Sman865

12
@ Sman865 - будь ласка, повірте мені, коли я кажу вам, що це насправді майже напевно найскладніша відповідь, запропонована тут, і вона також помилкова в більшості аспектів - особливо в її вступній декларації. Будь-яке питання, пов'язане з присвоєнням значення, жодним чином не може бути пов'язане з його подальшим розширенням - це лише назад. Мені шкода Каннана, але ця відповідь є і неправильною, і неправильною. Розширення розбиття на поля $IFS- це потужний і універсальний інструмент, але написаний таким чином код ніколи не може дати надійних результатів будь-якого типу.
mikeserv

2
Якщо не використовувати лапки під час розширення змінної, рано чи пізно виникають проблеми, починаючи з розширення імені файлу (будь-якого з * ? []).
mr.spuratic

Я погоджуюсь, що змінні потрібно укласти в подвійні лапки, щоб запобігти розширенню bash. Але для особливих випадків ми можемо уникати цього, щоб запобігти ускладненню коду.
Каннан Мохан

1
Я погоджуюся з @mikeserv, це дуже погана порада, якщо її прийняти за номінал. Це буде порушено, якщо використовувати його без наслідків. Наприклад, якщо змінна передається як аргумент команді, ви не хочете, щоб кожне слово було розбито як окремий аргумент.
haridsv

28

Рішення, надані esuoxu та Mickaël Bucas - це поширені та більш портативні способи цього зробити.

Ось декілька bashрішень (деякі з яких також повинні працювати в інших оболонках, як zsh). По-перше, з +=оператором додавання (який працює дещо по-різному для кожної цілочисельної змінної, регулярної змінної та масиву).

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

Якщо ви хочете, щоб у тексті були нові рядки (або інші пробіли / пробіли), $''замість цього використовуйте цитування:

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

Далі, використовуючи printf -vдля призначення зміненого значення відформатоване

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

Хитрість тут полягає в тому, що аргументів більше, ніж специфікаторів формату, тому на відміну від більшості printfфункцій, bash використовує рядок формату, поки не закінчиться. Ви можете ввести \nрядок формату або використовувати $ '', (або обидва) для роботи з пробілом.

Далі, використовуючи масив:

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

Ви також +=можете створити текст вгору за рядком (зверніть увагу на ()):

text+=("post script")

Тут ви повинні пам’ятати, що «вирівняти» масив, якщо ви хочете весь текстовий вміст за один раз

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

(цілочисельні індексовані масиви неявно сортуються, на відміну від асоціативних масивів) Це дає вам трохи більше гнучкості, оскільки ви можете маніпулювати лініями та навіть зрізати і кістки, якщо потрібно.

Нарешті, використовуючи readабо readarrayі "тут-документ":

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  

Форма документа тут <<-означає, що всі провідні вкладки видаляються із вхідних даних, тому для відступу тексту потрібно використовувати вкладки. Цитати навколо "EOT"запобігають функції розширення оболонки, тому введення використовується дослівно. З readйого допомогою використовується вхід з обмеженим байтом NUL, так що він буде читати текст з обмеженим числом у новому рядку за один раз. З readarray(aka mapfile, доступний з bash-4.0) він зчитується в масив і -tзнімає нові рядки в кожному рядку.


1
readВаріант з here documentдуже приємно! Корисно вбудовувати сценарії python та виконувати за допомогою python -c. Раніше я робив script=$(cat <here document>), але read -r -d '' script <here document>набагато краще.
haridsv

9

Існує спеціальний синтаксис heredoc, який видаляє вкладки на початку всіх рядків: "<< -" (зверніть увагу на доданий тире)

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

Приклад 19-4. Багаторядкове повідомлення з придушеними вкладками

Ви можете використовувати його так:

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

Результат:

A
B
C

Він працює лише з вкладками, а не з пробілами.


Тут на ubuntu справжнє зворотне - пробіли працюють, а не вкладки. \tчудово працює, хоча вам потрібні вкладки. Просто використовуйте, echo -e "$v"а не echo "$v"вмикайте символи зворотної косої риси
will-ob

5

Нехай шкаралупа їсть небажані корми та наступні пробіли:

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

Так що можливо ... але впевнено, що це рішення смаку подобається чи не подобається ...


3
кожен з них - виделка.
mikeserv


5

Ось як я пропоную вам це зробити, і я поясню чому, але спершу я хочу поговорити про щось інше ...

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

Багато інших запропонованих тут рішень начебто підказують, що ви можете якось вплинути на вміст змінної оболонки, змінивши свої методи розширення. Можу запевнити, що це не так.

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

ВИХІД

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

Те, що ви бачите вище, - це спочатку розширення за розбиттям по полях, потім звіт про кількість байтів для вихідної змінної розширення, потім розширення з обмеженнями цитатами і те саме підрахунок байтів. Хоча вихід може відрізнятися, вміст змінної оболонки $stringвзагалі ніколи не змінюється, за винятком присвоєння.

Більше того, якщо ви не розумієте, чому це так, швидше за все ви зіткнетеся з дуже неприємними сюрпризами. Спробуємо це ще раз, але в дещо інших умовах.

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

Те саме $string- різне середовище.

ВИХІД

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

Розщеплення поля відбувається на основі роздільників поля, визначених у $IFS. Існує два види роздільників - $IFSпробіл та $IFSвсе інше. За замовчуванням $IFSпризначається вкладка$IFS пробілу значень newline - це три можливі значення пробілу. Це легко змінюється, хоча, як ви бачите вище, і може мати різкий вплив на розширення по полях.

$IFSпробіл буде витікати послідовно в одне поле - і саме тому echoрозширення, що містить будь-яку послідовність пробілів, коли $IFSмістить пробіл, буде оцінюватися лише на один простір, тому що echoоб'єднує свої аргументи на простори. Але будь-які значення, що не мають пробілу, не зміщуватимуться однаково, і кожен зустрічається роздільник завжди отримує поле для себе - як це видно з розширення матеріалів вище.

Це не найгірше. Розглянемо це інше $string.

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

ВИХІД

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

Виглядає нормально, правда? Що ж, давайте ще раз змінити середовище.

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

ВИХІД

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

Вуа.

За замовчуванням оболонка розширить кулі імен файлів, якщо вони можуть відповідати їм. Це відбувається після розширення параметра та розбиття поля в його порядку розбору, тому будь-яка не котирувана рядок є вразливою таким чином. Ви можете вимкнути цю поведінку, set -fякщо хочете, але будь-яка сумісна з POSIX оболонка завжди буде глобальною за замовчуванням.

Це та річ, проти якої ви ставитесь, коли ви кидаєте котирування на розширення відповідно до ваших переваг відступу. І навіть у будь-якому випадку, незалежно від поведінки щодо розширення, фактичне значення $stringзавжди залишається таким, яким воно було, коли ви востаннє присвоювали його. Тож повернемось до першого.

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

ВИХІД

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

Я вважаю, що це набагато безпечніший спосіб адаптувати синтаксис оболонки до ваших уподобань. Я роблю вище - присвоюю кожній окремій рядку позиційний параметр, який може посилатися на номер типу $1або ${33}-, а потім присвоювати їх об'єднані значення за $varдопомогою спеціального параметра оболонки $*.

Цей підхід не застрахований від $IFSцього. І все-таки я вважаю її відношення до $IFSдодаткової вигоди в цьому відношенні. Поміркуйте:

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

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

ВИХІД

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.

Як бачите, $*об'єднайте кожен аргумент у "$@"перший байт в $IFS. Таким чином, збереження його значення, а $IFSінше призначається, отримує різні роздільники поля для кожного збереженого значення. До речі, те, що ви бачите вище - це буквальне значення для кожної змінної. Якби ви взагалі не хотіли розмежувача, зробите:

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

ВИХІД

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

2

Ви можете спробувати:

echo $VAR | tr -s " "

або

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

а також ви можете це перевірити .


2

Використовуйте розширення Bash Substitution

Якщо ви використовуєте Bash, ви можете використовувати розширення заміни . Наприклад:

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

1

Це варіант встановлення змінних, що нагадують шлях:

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)

Використання setперезаписів $@, які можна зберегти та використати пізніше:

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

LD_LIBRARY_PATH=$(sed 's/ :/:/g' <<< $LD_LIBRARY_PATH)Лінії усуває прогалини перед двокрапкою, а також можливого замикають прогалини. Якщо виключаєте лише пробіли, використовуйте LD_LIBRARY_PATH=${LD_LIBRARY_PATH%% }замість цього.

Весь цей підхід є варіантом на відмінну відповідь mikeserv.


0

Не бійтеся символів пробілів. Просто видаліть їх перед тим, як надрукувати багаторядковий текст.

$ 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

Вихід:

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

Не потрібно використовувати <<-гередок і розбивати 4-пробільний відступ символами вкладки.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.