Làm cách nào để đưa văn bản HEREDOC vào biến kịch bản shell?


9

Tôi đang cố gắng đưa văn bản HEREDOC vào một biến kịch bản shell theo cách tuân thủ POSIX. Tôi đã cố gắng như vậy:

#!/bin/sh

NEWLINE="
"

read_heredoc2() {
  while IFS="$NEWLINE" read -r read_heredoc_line; do
    echo "${read_heredoc_line}"
  done
}

read_heredoc2_result="$(read_heredoc2 <<'HEREDOC'

                        _                            _ _
                       | |                          | (_)
  _ __ ___  _   _ _ __ | | __ _  ___ ___  ___  _ __ | |_ _ __   ___
 | '_ ` _ \| | | | '_ \| |/ _` |/ __/ _ \/ _ \| '_ \| | | '_ \ / _ \
 | | | | | | |_| | |_) | | (_| | (_|  __/ (_) | | | | | | | | |  __/
 |_| |_| |_|\__, | .__/|_|\__,_|\___\___|\___/|_| |_|_|_|_| |_|\___|
             __/ | |
            |___/|_|



HEREDOC
)"

echo "${read_heredoc2_result}"

Điều đó tạo ra những điều sau đây là sai:

                        _                            _ _
                       | |                          | (_)
  _ __ ___  _   _ _ __ | | __ _  ___ ___  ___  _ __ | |_ _ __   ___
 | '_ ` _ \| | | | '_ \| |/ _` |/ __/ _ \/ _ \| '_ \| | | '_ \ / _  | | | | | | |_| | |_) | | (_| | (_|  __/ (_) | | | | | | | | |  __/
 |_| |_| |_|\__, | .__/|_|\__,_|\___\___|\___/|_| |_|_|_|_| |_|\___|
             __/ | |
            |___/|_|

Các công việc sau đây nhưng tôi không thích cách nó cồng kềnh bằng cách sử dụng biến đầu ra ngẫu nhiên:

#!/bin/sh

NEWLINE="
"

read_heredoc1() {
  read_heredoc_first=1
  read_heredoc_result=""
  while IFS="$NEWLINE" read -r read_heredoc_line; do
    if [ ${read_heredoc_first} -eq 1 ]; then
      read_heredoc_result="${read_heredoc_line}"
      read_heredoc_first=0
    else
      read_heredoc_result="${read_heredoc_result}${NEWLINE}${read_heredoc_line}"
    fi
  done
}

read_heredoc1 <<'HEREDOC'

                        _                            _ _            
                       | |                          | (_)           
  _ __ ___  _   _ _ __ | | __ _  ___ ___  ___  _ __ | |_ _ __   ___ 
 | '_ ` _ \| | | | '_ \| |/ _` |/ __/ _ \/ _ \| '_ \| | | '_ \ / _ \
 | | | | | | |_| | |_) | | (_| | (_|  __/ (_) | | | | | | | | |  __/
 |_| |_| |_|\__, | .__/|_|\__,_|\___\___|\___/|_| |_|_|_|_| |_|\___|
             __/ | |                                                
            |___/|_|                                                



HEREDOC

echo "${read_heredoc_result}"

Đầu ra đúng:

                        _                            _ _            
                       | |                          | (_)           
  _ __ ___  _   _ _ __ | | __ _  ___ ___  ___  _ __ | |_ _ __   ___ 
 | '_ ` _ \| | | | '_ \| |/ _` |/ __/ _ \/ _ \| '_ \| | | '_ \ / _ \
 | | | | | | |_| | |_) | | (_| | (_|  __/ (_) | | | | | | | | |  __/
 |_| |_| |_|\__, | .__/|_|\__,_|\___\___|\___/|_| |_|_|_|_| |_|\___|
             __/ | |                                                
            |___/|_|                                                

Có ý kiến ​​gì không?


Nếu banner chỉ được sử dụng một lần, hãy sử dụng cattrực tiếp với tài liệu này. Nếu nó được sử dụng ở nhiều nơi trong tập lệnh, hãy lưu nó vào tập tin cattừ đó, giống như /etc/motdđược sử dụng trên một số hệ thống.
Kusalananda

1
Lưu ý rằng vấn đề bạn đang gặp phải thực sự là một lỗi Bash - bạn đã có một giải pháp POSIX đã có trong nỗ lực ban đầu của bạn, mà sẽ làm việc tốt trong ksh, dash, ash, và lâu đời nhất Bourne shell Tôi có thể tìm thấy. Phân tích cú pháp thay thế lệnh Bash là lạ, và được sử dụng để thậm chí bị hỏng nhiều hơn.
Michael Homer

@MichaelHomer Ôi, thật thú vị! Ồ, tôi đang ở Fedora 25 mới nhất, bash 4.3.43-4.fc25
Kevin

1
Vâng. Nếu bạn có thể có phiên bản 3-series để thử kịch bản, nó sẽ bị hỏng ngay cả ở lần backtick đầu tiên, vì vậy nó sẽ được cải thiện. Tôi không chắc liệu họ coi đó là một lỗi - cho là POSIX không rõ ràng cấm hành vi này, nhưng nó khá rõ ràng rằng lệnh thay chứa tất cả các nhân vật cho đến khi )và heredocs trích dẫn không có mở rộng.
Michael Homer

Câu trả lời:


11

Vấn đề là ở Bash , $( ... )các chuỗi thoát bên trong (và các thứ khác) bị phân tách, mặc dù chính di sản sẽ không có chúng. Bạn nhận được một dòng nhân đôi vì \thoát khỏi ngắt dòng. Những gì bạn đang thấy thực sự là một vấn đề phân tích cú pháp trong Bash - các shell khác không làm điều này. Backticks cũng có thể là một vấn đề trong các phiên bản cũ hơn. Tôi đã xác nhận rằng đây là một lỗi trong Bash và nó sẽ được sửa trong các phiên bản trong tương lai.

Bạn ít nhất có thể đơn giản hóa chức năng của mình một cách quyết liệt:

func() {
    res=$(cat)
}
func <<'HEREDOC'
...
HEREDOC

Nếu bạn muốn chọn biến đầu ra, nó có thể được tham số hóa:

func() {
    eval "$1"'=$(cat)'
}
func res<<'HEREDOC'
...
HEREDOC

Hoặc một cái khá xấu xí mà không có eval:

{ res=$(cat) ; } <<'HEREDOC'
...
HEREDOC

Cái {}cần thiết, hơn là (), để biến vẫn có sẵn sau đó.

Tùy thuộc vào tần suất bạn sẽ làm điều này và đến cuối cùng, bạn có thể thích một hoặc một trong các tùy chọn này. Cái cuối cùng là ngắn gọn nhất cho một lần.


Nếu bạn có thể sử dụng zsh, thay thế lệnh ban đầu của bạn + heredoc sẽ hoạt động như bình thường, nhưng bạn cũng có thể thu gọn tất cả những điều này xuống hơn nữa:

x=$(<<'EOT'
...
EOT
)

Bash không hỗ trợ điều này và tôi không nghĩ bất kỳ vỏ nào khác sẽ gặp phải vấn đề bạn gặp phải.


Một vấn đề tôi tìm thấy đó $(cat)là tước bỏ các dòng mới
Kevin

1
@Kevin Cách giải quyết ở đây có giúp được không?
Eliah Kagan

@EliahKagan Cảm ơn, điều đó có thể giúp ích, tôi sẽ thử chúng.
Kevin

@EliahKagan Tôi đã thêm một câu trả lời mới bên dưới.
Kevin

5

Về giải pháp OP:

  • Bạn không cần một eval để gán một biến nếu bạn cho phép một số biến không đổi được sử dụng.

  • cấu trúc chung của việc gọi một chức năng nhận HEREDOC cũng có thể được thực hiện.

Một giải pháp hoạt động trong tất cả các shell (hợp lý) với cả hai mục được giải quyết là:

#!/bin/bash
nl="
"

read_heredoc(){
    var=""
    while IFS="$nl" read -r line; do
        var="$var$line$nl"
    done 
}


read_heredoc <<'HEREDOC'

                        _                            _ _
                       | |                          | (_)
  _ __ ___  _   _ _ __ | | __ _  ___ ___  ___  _ __ | |_ _ __   ___
 | '_ ` _ \| | | | '_ \| |/ _` |/ __/ _ \/ _ \| '_ \| | | '_ \ / _ \
 | | | | | | |_| | |_) | | (_| | (_|  __/ (_) | | | | | | | | |  __/
 |_| |_| |_|\__, | .__/|_|\__,_|\___\___|\___/|_| |_|_|_|_| |_|\___|
             __/ | |
            |___/|_|

HEREDOC

read_heredoc2_result="$str"

printf '%s' "${read_heredoc2_result}"

Một giải pháp cho câu hỏi ban đầu.

Một giải pháp hoạt động kể từ bash 2.04 (và zsh, lksh, mksh gần đây).
Nhìn bên dưới để biết phiên bản di động hơn (POSIX).

#!/bin/bash
read_heredoc() {
    IFS='' read -d '' -r var <<'HEREDOC'

                        _                            _ _
                       | |                          | (_)
  _ __ ___  _   _ _ __ | | __ _  ___ ___  ___  _ __ | |_ _ __   ___
 | '_ ` _ \| | | | '_ \| |/ _` |/ __/ _ \/ _ \| '_ \| | | '_ \ / _ \
 | | | | | | |_| | |_) | | (_| | (_|  __/ (_) | | | | | | | | |  __/
 |_| |_| |_|\__, | .__/|_|\__,_|\___\___|\___/|_| |_|_|_|_| |_|\___|
             __/ | |
            |___/|_|



HEREDOC

}

read_heredoc
echo "$var"

Lệnh cốt lõi

IFS='' read -d '' -r var <<'HEREDOC'

hoạt động như sau:

  1. Từ HEREDOCnày là (đơn) được trích dẫn để tránh bất kỳ sự mở rộng nào của văn bản theo sau.
  2. Nội dung "ở đây doc" được phục vụ trong stdin với <<.
  3. Tùy chọn -d ''buộc readphải làm lu mờ toàn bộ nội dung của "tài liệu ở đây".
  4. Các -rtùy chọn tránh giải thích xuyệc ngược trích dẫn ký tự.
  5. Lệnh cốt lõi tương tự như read var.
  6. Và chi tiết cuối cùng là IFS='', sẽ tránh việc đọc loại bỏ các ký tự đầu hoặc cuối trong IFS mặc định : spacetabnewline.

Trong ksh, giá trị null cho -d ''tùy chọn không hoạt động.
Như một giải pháp thay thế, nếu văn bản không có "lợi nhuận vận chuyển", một -d $'\r'tác phẩm (nếu một $'\r'được thêm vào cuối mỗi dòng, tất nhiên).


Yêu cầu được thêm vào (trong nhận xét) là tạo ra giải pháp tuân thủ POSIX.

POSIX

Mở rộng ý tưởng để làm cho nó chỉ chạy với các tùy chọn POSIX.
Điều đó có nghĩa là chủ yếu là không -dcho read. Điều đó buộc một đọc cho mỗi dòng.
Điều đó, đến lượt nó buộc phải nắm bắt một dòng tại một thời điểm.
Sau đó, để xây dựng varmột dòng mới phải được thêm vào (khi đọc đã loại bỏ nó).

#!/bin/sh

nl='
'

read_heredoc() {
    unset var
    while IFS="$nl" read -r line; do
        var="$var$line$nl"
    done <<\HEREDOC

                        _                            _ _
                       | |                          | (_)
  _ __ ___  _   _ _ __ | | __ _  ___ ___  ___  _ __ | |_ _ __   ___
 | '_ ` _ \| | | | '_ \| |/ _` |/ __/ _ \/ _ \| '_ \| | | '_ \ / _ \ 
 | | | | | | |_| | |_) | | (_| | (_|  __/ (_) | | | | | | | | |  __/ 
 |_| |_| |_|\__, | .__/|_|\__,_|\___\___|\___/|_| |_|_|_|_| |_|\___| 
             __/ | | 
            |___/|_| 



HEREDOC

}

read_heredoc
printf '%s' "$var"

Điều đó hoạt động (và đã được thử nghiệm) trong tất cả các vỏ hợp lý.


2

Việc sử dụng mèo vô dụng (quote \ và `):

myplaceonline="
                       _                            _ _            
 _ __ ___  _   _ _ __ | | __ _  ___ ___  ___  _ __ | (_)_ __   ___ 
| '_ \` _ \\| | | | '_ \\| |/ _\` |/ __/ _ \\/ _ \\| '_ \\| | | '_ \\ / _ \\
| | | | | | |_| | |_) | | (_| | (_|  __/ (_) | | | | | | | | |  __/
|_| |_| |_|\\__, | .__/|_|\\__,_|\\___\\___|\\___/|_| |_|_|_|_| |_|\\___|
       |___/|_
"

Hoặc không trích dẫn:

myplaceonline="$(figlet myplaceonline)"

Latter không phải là POSIX.
phk

bạn đã đúng, bây giờ nó phải thế nào?
ctx

1
@ctx Cảm ơn câu trả lời, nhưng một yêu cầu không được nêu ra của tôi là tôi muốn có thể mang các HEREDOC không được trích dẫn (một phần vì tôi biến nó thành một hàm API công khai cho một thư viện có tên là posixcube). Câu trả lời được chấp nhận cuối cùng là chính xác rằng có một HEREDOC xử lý lỗi lồng nhau thay thế lệnh (xem nhận xét từ Michael Homer trong câu hỏi liên kết đến một câu hỏi khác liên kết đến danh sách gửi thư Bash nơi lỗi được xác nhận bởi người duy trì Bash) . Câu trả lời của tôi ở trên vào ngày 29 tháng 1 là giải pháp khắc phục của tôi và hoạt động tốt.
Kevin

1

Để hỗ trợ các dòng mới, tôi đã kết hợp câu trả lời từ @MichaelHomer và giải pháp ban đầu của tôi. Tôi đã không sử dụng các cách giải quyết được đề xuất từ ​​liên kết mà @EliahKagan lưu ý vì cái đầu tiên sử dụng chuỗi ma thuật và hai cái cuối cùng không tuân thủ POSIX.

#!/bin/sh

NEWLINE="
"

read_heredoc() {
  read_heredoc_result=""
  while IFS="${NEWLINE}" read -r read_heredoc_line; do
    read_heredoc_result="${read_heredoc_result}${read_heredoc_line}${NEWLINE}"
  done
  eval $1'=${read_heredoc_result}'
}

read_heredoc heredoc_str <<'HEREDOC'

                        _                            _ _
                       | |                          | (_)
  _ __ ___  _   _ _ __ | | __ _  ___ ___  ___  _ __ | |_ _ __   ___
 | '_ ` _ \| | | | '_ \| |/ _` |/ __/ _ \/ _ \| '_ \| | | '_ \ / _ \
 | | | | | | |_| | |_) | | (_| | (_|  __/ (_) | | | | | | | | |  __/
 |_| |_| |_|\__, | .__/|_|\__,_|\___\___|\___/|_| |_|_|_|_| |_|\___|
             __/ | |
            |___/|_|




HEREDOC

echo "${heredoc_str}"

@sorontar Tôi mới thử nó và dòng mới dường như không bị xóa đối với tôi. Tôi không chắc ý của bạn là gì khi nói đến việc sử dụng một biến để đọc dòng đầu tiên. Về eval, nó chỉ được sử dụng cho tên của biến "đầu ra". Nếu chúng tôi giả sử người dùng đáng tin cậy của chức năng, có bất kỳ vấn đề nào khác sử dụng evaltrong ví dụ này không?
Kevin

@sorontar Điểm thú vị về dòng mới. Một tài liệu ở đây phải kết thúc bằng một dòng mới, tiếp theo là dấu phân cách và dòng mới, nhưng nó không cho biết dòng mới trước dấu phân cách có phải là một phần của chuỗi hay không: pubs.opengroup.org/onlinepub/9699919799/utilities /
Kevin

1
Có, Tài liệu ở đây phải kết thúc bằng một dòng mới và bạn mã loại bỏ nó. Mỗi " textdòng phải kết thúc trên một dòng mới". Tìm kiếm trong định nghĩa của tập tin văn bản.
Isaac


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.