Làm thế nào tôi có thể có một dòng mới trong một chuỗi trong sh?


337

Điều này

STR="Hello\nWorld"
echo $STR

sản xuất như đầu ra

Hello\nWorld

thay vì

Hello
World

Tôi nên làm gì để có một dòng mới trong một chuỗi?

Lưu ý: Câu hỏi này không phải là về tiếng vang . Tôi biết echo -e, nhưng tôi đang tìm một giải pháp cho phép chuyển một chuỗi (bao gồm một dòng mới) làm đối số cho các lệnh khác không có tùy chọn tương tự để diễn giải \ncác dòng mới.

Câu trả lời:


353

Giải pháp là sử dụng $'string', ví dụ:

$ STR=$'Hello\nWorld'
$ echo "$STR" # quotes are required here!
Hello
World

Đây là một đoạn trích từ trang hướng dẫn Bash:

   Words of the form $'string' are treated specially.  The word expands to
   string, with backslash-escaped characters replaced as specified by  the
   ANSI  C  standard.  Backslash escape sequences, if present, are decoded
   as follows:
          \a     alert (bell)
          \b     backspace
          \e
          \E     an escape character
          \f     form feed
          \n     new line
          \r     carriage return
          \t     horizontal tab
          \v     vertical tab
          \\     backslash
          \'     single quote
          \"     double quote
          \nnn   the eight-bit character whose value is  the  octal  value
                 nnn (one to three digits)
          \xHH   the  eight-bit  character  whose value is the hexadecimal
                 value HH (one or two hex digits)
          \cx    a control-x character

   The expanded result is single-quoted, as if the  dollar  sign  had  not
   been present.

   A double-quoted string preceded by a dollar sign ($"string") will cause
   the string to be translated according to the current  locale.   If  the
   current  locale  is  C  or  POSIX,  the dollar sign is ignored.  If the
   string is translated and replaced, the replacement is double-quoted.

74
Đó là bash hợp lệ, nhưng không phải là POSIX sh.
jmanning2k

7
+ 1-chỉ là một ghi chú: export varDocate = "$ varAnther1 $ varAnother2" $ '\ n'
Yordan Georgiev

3
Điều này thực sự hoạt động với các chuỗi ký tự như OP yêu cầu, nhưng thất bại ngay khi các biến được thay thế: STR=$"Hello\nWorld"chỉ đơn giản là in Hello\nWorld.
ssc

3
@ssc trích dẫn đơn, không gấp đôi
miken32

7
@ssc không có biến trong ví dụ của bạn. Ngoài ra, phương pháp này yêu cầu trích dẫn duy nhất. Cuối cùng, để bao gồm các biến được nội suy, bạn sẽ phải nối các chuỗi được trích dẫn kép và chuỗi trích dẫn đặc biệt với nhau. ví dụ: H="Hello"; W="World"; STR="${H}"$'\n'"${W}"; echo "${STR}"sẽ lặp lại "Xin chào" và "Thế giới" trên các dòng riêng biệt
JDS

166

Echo là những năm chín mươi và đầy nguy hiểm đến mức việc sử dụng nó sẽ dẫn đến kết xuất lõi không dưới 4GB. Nghiêm túc mà nói, các vấn đề của echo là lý do tại sao quá trình Tiêu chuẩn hóa Unix cuối cùng đã phát minh ra printftiện ích, loại bỏ tất cả các vấn đề.

Vì vậy, để có được một dòng mới trong một chuỗi:

FOO="hello
world"
BAR=$(printf "hello\nworld\n") # Alternative; note: final newline is deleted
printf '<%s>\n' "$FOO"
printf '<%s>\n' "$BAR"

Đó! Không có sự điên rồ của SYSV và BSD, mọi thứ đều được in gọn gàng và hỗ trợ di động hoàn toàn cho các chuỗi thoát C. Mọi người hãy sử dụng printfngay bây giờ và không bao giờ nhìn lại.


3
Cảm ơn câu trả lời này, tôi nghĩ rằng nó cung cấp lời khuyên thực sự tốt. Tuy nhiên, lưu ý rằng câu hỏi ban đầu của tôi không thực sự là về cách lấy tiếng vang để in các dòng mới, mà là làm thế nào để đưa một dòng mới vào một biến shell. Bạn chỉ cho bạn cách sử dụng printf, nhưng tôi nghĩ rằng giải pháp từ amphetamachine sử dụng $'string'thậm chí còn sạch hơn.
Juan A. Navarro

11
Chỉ cho một định nghĩa thiếu 'sạch'. Các $'foo'cú pháp không phải là cú pháp POSIX giá trị như của năm 2013. Nhiều vỏ sẽ phàn nàn.
Jens

5
BAR=$(printf "hello\nworld\n")không in dấu vết \ n cho tôi
Jonny

3
@Jonny Nó không nên; đặc tả shell để thay thế lệnh nói rằng một hoặc nhiều dòng mới ở cuối đầu ra bị xóa. Tôi nhận ra điều này là bất ngờ đối với ví dụ của tôi và đã chỉnh sửa nó để đưa một dòng mới vào printf.
Jens

5
@ismael Không, $()được POSIX chỉ định là loại bỏ các chuỗi của một hoặc nhiều ký tự <newline> ở cuối thay thế .
Jens

61

Những gì tôi đã làm dựa trên các câu trả lời khác là

NEWLINE=$'\n'
my_var="__between eggs and bacon__"
echo "spam${NEWLINE}eggs${my_var}bacon${NEWLINE}knight"

# which outputs:
spam
eggs__between eggs and bacon__bacon
knight

29

Vấn đề không phải là vỏ. Vấn đề thực sự là với echochính lệnh và thiếu dấu ngoặc kép xung quanh phép nội suy biến. Bạn có thể thử sử dụng echo -enhưng điều đó không được hỗ trợ trên tất cả các nền tảng và một trong những lý do printfhiện được khuyến nghị cho tính di động.

Bạn cũng có thể thử và chèn dòng mới trực tiếp vào tập lệnh shell của mình (nếu tập lệnh là những gì bạn đang viết) để nó trông giống như ...

#!/bin/sh
echo "Hello
World"
#EOF

hoặc tương đương

#!/bin/sh
string="Hello
World"
echo "$string"  # note double quotes!

23

Tôi thấy -elá cờ tao nhã và thẳng tiến

bash$ STR="Hello\nWorld"

bash$ echo -e $STR
Hello
World

Nếu chuỗi là đầu ra của lệnh khác, tôi chỉ sử dụng dấu ngoặc kép

indexes_diff=$(git diff index.yaml)
echo "$indexes_diff"

2
Điều này đã được đề cập trong các câu trả lời khác. Ngoài ra, mặc dù nó đã có sẵn, tôi vừa chỉnh sửa câu hỏi để nhấn mạnh thực tế rằng câu hỏi này không phải là về echo.
Juan A. Navarro

Đây không phải là một câu trả lời cho câu hỏi
Rohit Gupta

Điều này hoàn toàn trả lời câu hỏi tôi đang tìm kiếm! Cảm ơn!
Kieveli

1
Làm việc tốt cho tôi ở đây. Tôi đã từng tạo một tệp dựa trên tiếng vang này và nó đã tạo ra các dòng mới trên đầu ra một cách chính xác.
Mateus Leon

echo -enổi tiếng với các vấn đề di động của nó. Điều này ít xảy ra với tình huống miền Tây hoang dã hơn so với tiền POSIX, nhưng vẫn không có lý do nào để thích echo -e. Xem thêm stackoverflow.com/questions/11530203/ khăn
tripleee 28/03/19

21
  1. Cách thay thế đơn giản duy nhất là thực sự gõ một dòng mới trong biến:

    $ STR='new
    line'
    $ printf '%s' "$STR"
    new
    line

    Vâng, điều đó có nghĩa là viết Enternơi cần thiết trong mã.

  2. Có một số tương đương với một new linenhân vật.

    \n           ### A common way to represent a new line character.
    \012         ### Octal value of a new line character.
    \x0A         ### Hexadecimal value of a new line character.

    Nhưng tất cả những yêu cầu "giải thích" bởi một số công cụ ( POSIX printf ):

    echo -e "new\nline"           ### on POSIX echo, `-e` is not required.
    printf 'new\nline'            ### Understood by POSIX printf.
    printf 'new\012line'          ### Valid in POSIX printf.
    printf 'new\x0Aline'       
    printf '%b' 'new\0012line'    ### Valid in POSIX printf.

    Và do đó, công cụ được yêu cầu để xây dựng một chuỗi với một dòng mới:

    $ STR="$(printf 'new\nline')"
    $ printf '%s' "$STR"
    new
    line
  3. Trong một số shell, chuỗi $'là một mở rộng shell đặc biệt. Được biết để làm việc trong ksh93, bash và zsh:

    $ STR=$'new\nline'
  4. Tất nhiên, các giải pháp phức tạp hơn cũng có thể:

    $ echo '6e65770a6c696e650a' | xxd -p -r
    new
    line

    Hoặc là

    $ echo "new line" | sed 's/ \+/\n/g'
    new
    line

6

$ Ngay trước dấu ngoặc kép đơn '... \ n ...' như sau, tuy nhiên dấu ngoặc kép không hoạt động.

$ echo $'Hello\nWorld'
Hello
World
$ echo $"Hello\nWorld"
Hello\nWorld

4

Tôi không phải là chuyên gia bash, nhưng cái này hiệu quả với tôi:

STR1="Hello"
STR2="World"
NEWSTR=$(cat << EOF
$STR1

$STR2
EOF
)
echo "$NEWSTR"

Tôi thấy điều này dễ dàng hơn để định dạng các văn bản.


2
Tốt cũ di truyền . Và có lẽ nó cũng tuân thủ POSIX!
pyb

Các dấu ngoặc kép quanh $NEWSTRtrong echo "$NEWSTR"rất quan trọng ở đây
Shadi

0

Trên hệ thống của tôi (Ubuntu 17.10), ví dụ của bạn chỉ hoạt động như mong muốn, cả khi được nhập từ dòng lệnh (vào sh) và khi được thực thi dưới dạng shtập lệnh:

[bash sh
$ STR="Hello\nWorld"
$ echo $STR
Hello
World
$ exit
[bash echo "STR=\"Hello\nWorld\"
> echo \$STR" > test-str.sh
[bash cat test-str.sh 
STR="Hello\nWorld"
echo $STR
[bash sh test-str.sh 
Hello
World

Tôi đoán điều này trả lời câu hỏi của bạn: nó chỉ hoạt động. (Tôi đã không cố gắng tìm ra các chi tiết như tại thời điểm nào chính xác việc thay thế ký tự dòng mới \nxảy ra trongsh ).

Tuy nhiên, tôi nhận thấy rằng kịch bản này tương tự sẽ hành xử khác nhau khi thực hiện vớibash và sẽ in ra Hello\nWorldthay vì:

[bash bash test-str.sh
Hello\nWorld

Tôi đã quản lý để có được đầu ra mong muốn bashnhư sau:

[bash STR="Hello
> World"
[bash echo "$STR"

Lưu ý các dấu ngoặc kép xung quanh $STR. Điều này hành xử giống hệt nếu được lưu và chạy như mộtbash tập lệnh.

Sau đây cũng cho đầu ra mong muốn:

[bash echo "Hello
> World"

0

Những người kén chọn chỉ cần dòng mới và coi thường mã đa dòng phá vỡ thụt lề, có thể làm:

IFS="$(printf '\nx')"
IFS="${IFS%x}"

Bash (và có thể là các shell khác) ngấu nghiến tất cả các dòng mới sau khi thay thế lệnh, vì vậy bạn cần kết thúc printfchuỗi bằng một ký tự không phải dòng mới và xóa nó sau đó. Điều này cũng có thể dễ dàng trở thành một oneliner.

IFS="$(printf '\nx')" IFS="${IFS%x}"

Tôi biết đây là hai hành động thay vì một, nhưng OCD thụt lề và tính di động của tôi hiện đã ổn định :) Tôi ban đầu đã phát triển điều này để có thể phân tách đầu ra chỉ tách dòng mới và cuối cùng tôi đã sử dụng một sửa đổi sử dụng \rlàm ký tự kết thúc. Điều đó làm cho việc phân chia dòng mới hoạt động ngay cả đối với đầu ra dos kết thúc bằng \r\n.

IFS="$(printf '\n\r')"

0

Tôi không thực sự hài lòng với bất kỳ lựa chọn nào ở đây. Đây là những gì làm việc cho tôi.

str=$(printf "%s" "first line")
str=$(printf "$str\n%s" "another line")
str=$(printf "$str\n%s" "and another line")

Đó là một cách rất tẻ nhạt để viết str=$(printf '%s\n' "first line" "another line" "and another line")(shell sẽ thuận tiện (hoặc không) cắt bất kỳ dòng mới nào cuối cùng từ sự thay thế lệnh). Một số câu trả lời ở đây đã được quảng bá printfdưới nhiều hình thức khác nhau, vì vậy tôi không chắc điều này sẽ thêm bất kỳ giá trị nào dưới dạng một câu trả lời riêng biệt.
tripleee

Nó dành cho các chuỗi ngắn, nhưng nếu bạn muốn giữ mọi thứ gọn gàng và mỗi dòng thực sự dài 60 ký tự, thì đó là một cách làm gọn gàng. Cho rằng đó là giải pháp cuối cùng tôi đã đi cùng, nó sẽ thêm một cái gì đó, nếu không phải cho bạn, sau đó cho bản thân tôi trong tương lai khi tôi chắc chắn trở lại.
Adam K Dean

1
Ngay cả đối với các chuỗi dài tôi thích printf '%s\n' "first line" \ (dòng mới, thụt lề), 'second line'v.v ... Xem thêm printf -v strđể gán cho một biến mà không sinh ra một chuỗi con.
tripleee 28/03/19
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.