Làm cách nào để gán giá trị chuỗi cho một biến qua nhiều dòng trong khi thụt lề?


54

Vấn đề:

  1. Tôi cần chỉ định một biến có giá trị dài.
  2. Tất cả các dòng trong kịch bản của tôi phải nằm dưới một số cột nhất định.

Vì vậy, tôi đang cố gắng gán nó bằng nhiều hơn một dòng.

Thật đơn giản để làm mà không cần thụt lề:

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

Kết quả:

This displays without any issues.

Tuy nhiên với thụt lề:

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

Kết quả:

This displays with      extra spaces.

Làm thế nào tôi có thể gán nó một cách thanh lịch mà không có những không gian này?

Câu trả lời:


30

Vấn đề ở đây là bạn đang bao quanh biến với dấu ngoặc kép (""). Loại bỏ nó và mọi thứ sẽ hoạt động tốt.

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

Đầu ra

 This displays with extra spaces.

Vấn đề ở đây là trích dẫn kép một biến bảo tồn tất cả các ký tự khoảng trắng. Điều này có thể được sử dụng trong trường hợp nếu bạn rõ ràng cần nó.

Ví dụ,

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

sẽ in

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

Và về việc loại bỏ dấu ngoặc kép, nó khác

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

Ở đây Bash loại bỏ các khoảng trắng thừa trong văn bản vì trong trường hợp đầu tiên, toàn bộ văn bản được lấy làm đối số "đơn" và do đó bảo toàn các khoảng trắng thừa. Nhưng trong trường hợp thứ hai, echolệnh nhận được văn bản là 5 đối số.

Trích dẫn một biến cũng sẽ hữu ích trong khi truyền đối số cho các lệnh.

Trong lệnh dưới đây, echochỉ nhận được đối số duy nhất là"Hello World"

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

Nhưng trong trường hợp kịch bản dưới đây echocó hai đối số là HelloWorld

$ variable="Hello World"
$ echo $variable

Hầu hết các câu trả lời đều hoạt động tốt, nhưng đây là cách đơn giản nhất. Cảm ơn!
Sman865

12
@ Sman865 - xin hãy tin tôi khi tôi nói với bạn rằng đây thực sự gần như chắc chắn là câu trả lời phức tạp nhất được đưa ra ở đây, và nó cũng sai ở hầu hết các khía cạnh - đặc biệt là trong tuyên bố mở đầu của nó. Bất kỳ vấn đề nào xung quanh việc gán giá trị không thể theo bất kỳ cách nào liên quan đến việc mở rộng sau này - điều đó chỉ là ngược. Tôi xin lỗi Kannan, nhưng câu trả lời này vừa sai vừa sai. Mở rộng phân chia trường trên $IFSlà một công cụ mạnh mẽ và phổ quát - nhưng mã được viết theo cách này không bao giờ có thể tạo ra kết quả đáng tin cậy dưới bất kỳ hình thức nào.
mikeerv

2
Không sử dụng dấu ngoặc kép khi mở rộng một biến gây ra vấn đề sớm hay muộn, bắt đầu bằng việc mở rộng tên tệp (bất kỳ * ? []).
mr.spuratic

Tôi đồng ý rằng các biến cần được đặt trong dấu ngoặc kép để ngăn không cho bash mở rộng. Nhưng đối với các trường hợp đặc biệt chúng ta có thể tránh nó để ngăn ngừa sự phức tạp của mã.
Kannan Mohan

1
Tôi đồng ý với @mikeerv, đây là một lời khuyên rất tệ nếu dùng theo mệnh giá. Điều này sẽ phá vỡ nếu sử dụng mà không có hậu quả. Ví dụ: nếu biến được truyền dưới dạng đối số cho lệnh, bạn không muốn mỗi từ bị chia thành một đối số riêng.
haridsv

28

Các giải pháp được đưa ra bởi esuoxuMickaël Bucas là những cách phổ biến và di động hơn để làm điều này.

Dưới đây là một vài bashgiải pháp (một số trong số đó cũng nên hoạt động trong các shell khác, như zsh). Đầu tiên với +=toán tử chắp thêm (hoạt động theo cách hơi khác nhau cho mỗi biến số nguyên, biến thông thường và mảng).

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

Nếu bạn muốn dòng mới (hoặc khoảng trắng / thoát khác) trong văn bản, hãy sử dụng $''trích dẫn thay thế:

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

Tiếp theo, sử dụng printf -vđể gán giá trị được định dạng cho một biến

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

Thủ thuật ở đây là có nhiều đối số hơn các chỉ định định dạng, do đó, không giống như hầu hết các printfhàm, bash one sử dụng lại chuỗi định dạng cho đến khi hết. Bạn có thể đặt một \nchuỗi trong định dạng hoặc sử dụng $ '', (hoặc cả hai) để xử lý khoảng trắng.

Tiếp theo, sử dụng một mảng:

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

Bạn cũng có thể sử dụng +=để xây dựng văn bản theo từng dòng (lưu ý ()):

text+=("post script")

Tuy nhiên, ở đây, bạn phải nhớ "làm phẳng" mảng nếu bạn muốn toàn bộ nội dung văn bản trong một lần

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

(mảng được lập chỉ mục số nguyên được sắp xếp ngầm, không giống như mảng kết hợp) Điều này cho phép bạn linh hoạt hơn một chút vì bạn có thể thao tác các dòng và thậm chí lát và xúc xắc nếu cần.

Cuối cùng, sử dụng readhoặc readarrayvà "tài liệu ở đây":

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  

Hình thức tài liệu ở đây <<-có nghĩa là tất cả các tab cứng hàng đầu được xóa khỏi đầu vào, vì vậy bạn phải sử dụng các tab để thụt lề văn bản của mình. Báo giá xung quanh "EOT"ngăn chặn các tính năng mở rộng vỏ, vì vậy đầu vào được sử dụng nguyên văn. Với readnó sử dụng đầu vào được phân tách bằng byte NUL, do đó nó sẽ đọc văn bản được phân tách bằng dòng mới trong một lần. Với readarray(aka mapfile, có sẵn từ bash-4.0), nó đọc thành một mảng và -tloại bỏ các dòng mới trên mỗi dòng.


1
Các readtùy chọn với here documentlà rất tốt đẹp! Hữu ích để nhúng các kịch bản python và thực hiện với python -c. Tôi đã từng làm script=$(cat <here document>)trước đây, nhưng read -r -d '' script <here document>tốt hơn nhiều.
haridsv

9

Có một cú pháp heredoc đặc biệt loại bỏ các tab ở đầu tất cả các dòng: "<< -" (chú ý dấu gạch ngang được thêm vào)

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

Ví dụ 19-4. Tin nhắn nhiều dòng, với các tab bị chặn

Bạn có thể sử dụng nó như thế này:

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

Kết quả :

A
B
C

Nó chỉ hoạt động với các tab, không phải khoảng trắng.


Ở đây trên Ubuntu, điều ngược lại là đúng - không gian hoạt động, không phải tab. \thoạt động tuyệt vời mặc dù nếu bạn cần các tab. Chỉ sử dụng echo -e "$v"chứ không phải echo "$v"để bật các ký tự dấu gạch chéo ngược
will-ob

5

Để vỏ ăn các dòng thức ăn không mong muốn và các khoảng trắng sau:

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

Vì vậy, có thể ... nhưng chắc chắn đó là vấn đề sở thích để thích hoặc không thích giải pháp này ...


3
mỗi một trong số đó là một ngã ba.
mikeerv

5

Có lẽ bạn có thể thử điều này.

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

5

Đây là cách tôi khuyên bạn nên làm điều đó, và tôi sẽ giải thích tại sao, nhưng trước tiên tôi muốn nói về điều gì khác ...

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

Rất nhiều giải pháp chuyên sâu khác ở đây dường như cho thấy rằng bằng cách nào đó bạn có thể ảnh hưởng đến nội dung của biến shell bằng cách thay đổi phương pháp mở rộng nó. Tôi có thể đảm bảo với bạn đây không phải là trường hợp.

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

ĐẦU RA

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

Những gì bạn thấy ở trên trước tiên là mở rộng phân chia trường, sau đó là báo cáo về số byte cho biến nguồn của mở rộng, sau đó là mở rộng được phân tách bằng trích dẫn và cùng một số đếm byte. Mặc dù đầu ra có thể khác nhau, nội dung của biến shell $stringkhông bao giờ thay đổi ngoại trừ khi gán.

Hơn nữa, nếu bạn không hiểu tại sao lại như vậy, bạn nhất định sẽ gặp phải một số bất ngờ rất khó chịu sớm hơn sau này. Hãy thử lại lần nữa, nhưng trong những điều kiện hơi khác nhau.

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

Giống $stringnhau - môi trường khác nhau.

ĐẦU RA

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

Phân tách trường xảy ra dựa trên các dấu phân cách trường được xác định trong $IFS. Có hai loại dấu phân cách - $IFSkhoảng trắng và $IFSbất cứ thứ gì khác. Theo mặc định $IFSđược gán dòng mới của tab không gian giá trị - là ba $IFSgiá trị khoảng trắng có thể có . Tuy nhiên, nó có thể dễ dàng thay đổi, như bạn có thể thấy ở trên, và có thể có tác động mạnh mẽ đến việc mở rộng phân chia trường.

$IFSkhoảng trắng sẽ tách biệt theo trình tự đến một trường duy nhất - và đây là lý do tại sao echoviệc mở rộng chứa bất kỳ chuỗi không gian nào khi $IFSchứa một khoảng trắng sẽ chỉ đánh giá một không gian duy nhất - bởi vì echonối các đối số của nó trên các khoảng trắng. Nhưng bất kỳ giá trị không phải khoảng trắng nào cũng sẽ không tồn tại theo cùng một cách và mỗi dấu phân cách xuất hiện luôn có một trường cho chính nó - như có thể thấy trong phần mở rộng công cụ ở trên.

Đây không phải là tồi tệ nhất của nó. Hãy xem xét cái này khác $string.

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

ĐẦU RA

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

Có vẻ ổn, phải không? Vâng, hãy thay đổi môi trường một lần nữa.

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

ĐẦU RA

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

Ái chà.

Theo mặc định, shell sẽ mở rộng các tên tập tin nếu nó có thể khớp với chúng. Điều này xảy ra sau khi mở rộng tham số và tách trường theo thứ tự phân tích cú pháp và do đó, bất kỳ chuỗi không trích dẫn nào cũng dễ bị tổn thương theo cách này. Bạn có thể tắt hành vi này set -fnếu bạn muốn, nhưng mọi vỏ tương thích POSIX sẽ luôn luôn toàn cầu theo mặc định.

Đây là loại nội dung bạn gặp phải khi bạn bỏ dấu ngoặc kép trên bản mở rộng cho phù hợp với sở thích thụt lề của bạn. Và ngay cả như vậy, trong mọi trường hợp, bất kể hành vi mở rộng của nó, giá trị thực tế $stringvẫn luôn là bất cứ khi nào bạn được chỉ định lần cuối. Vì vậy, hãy trở lại điều đầu tiên.

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

ĐẦU RA

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

Tôi tin rằng đây là một cách xa hơn để điều chỉnh cú pháp shell theo sở thích thụt lề của bạn. Những gì tôi đang làm ở trên là gán từng chuỗi riêng lẻ cho một tham số vị trí - mỗi chuỗi có thể được tham chiếu theo số như $1hoặc ${33}- và sau đó gán các giá trị được nối của chúng để $varsử dụng tham số shell đặc biệt $*.

Cách tiếp cận này không miễn dịch $IFS, thậm chí là như vậy. Tuy nhiên, tôi xem xét mối quan hệ của nó với $IFSmột lợi ích gia tăng về mặt này. Xem xét:

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

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

ĐẦU RA

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.

Như bạn có thể thấy, $*nối từng "$@"đối số trên byte đầu tiên trong $IFS. Vì vậy, việc lưu giá trị của nó trong khi $IFSđược gán khác nhau sẽ có các dấu phân cách trường khác nhau cho mỗi giá trị được lưu. Nhân tiện, những gì bạn thấy ở trên là giá trị bằng chữ cho mỗi biến. Nếu bạn không muốn có dấu phân cách nào cả, bạn sẽ làm:

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

ĐẦU RA

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

2

Bạn có thể muốn thử:

echo $VAR | tr -s " "

hoặc là

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

và bạn cũng có thể kiểm tra điều này .


2

Sử dụng bản mở rộng thay thế Bash

Nếu bạn đang sử dụng Bash, bạn có thể sử dụng mở rộng thay thế . Ví dụ:

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

1

Đây là một biến thể để thiết lập các biến giống như đường dẫn:

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)

Sử dụng setghi đè $@, có thể được lưu và sử dụng sau này như sau:

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

Các LD_LIBRARY_PATH=$(sed 's/ :/:/g' <<< $LD_LIBRARY_PATH)dòng loại bỏ khoảng trống trước dấu hai chấm cũng như khoảng trắng đuôi càng tốt. Nếu chỉ loại bỏ dấu cách, hãy sử dụng LD_LIBRARY_PATH=${LD_LIBRARY_PATH%% }thay thế.

Toàn bộ cách tiếp cận này là một biến thể của câu trả lời tuyệt vời của mikeerv.


0

Đừng sợ các ký tự khoảng trắng. Chỉ cần loại bỏ chúng trước khi bạn in văn bản nhiều dòng của bạn.

$ 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

Đầu ra:

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

Không cần sử dụng một <<-di sản và phá vỡ thụt lề 4 không gian của bạn với các ký tự tab.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.