Tại sao set-o errexit phá vỡ biểu thức đọc / heredoc này?


8

Tôi đã sử dụng mẫu dưới đây để in các tin nhắn đa dòng đến thiết bị đầu cuối trong một tập lệnh bash.

read -d '' message <<- EOF
    this is a 
    mulitline
    message
EOF
echo "$message"

Điều này đã hoạt động - cho đến một vài ngày trước, mô hình chỉ ngừng hoạt động. Bằng cách ngừng hoạt động, ý tôi là khi bash gặp phải các biểu thức di truyền này trong kịch bản - nó dường như không làm gì cả - không có đầu ra.
Điều duy nhất mà tôi có thể nghĩ về điều đó đã thay đổi trong vài ngày qua là môi trường mà các tập lệnh được chạy bên trong là USB sống 14.04, so với cài đặt "đầy đủ".
Sau đó, tôi phát hiện ra rằng khi tôi di chuyển di sản trước khi set -o errexittuyên bố kịch bản, nó bắt đầu hoạt động trở lại. tức là nó không hoạt động

#!/bin/bash

set -o errexit

read -d '' message <<- EOF
    this is a 
    mulitline
    message
EOF

echo "$message"

kết quả: (không có gì)
Nhưng điều này không hoạt động

#!/bin/bash

read -d '' message <<- EOF
    this is a 
    mulitline
    message
EOF

echo "$message"

kết quả

$ sudo ./script.sh 
this is a 
mulitline
message
  • bash - đảo ngược - GNU bash, version 4.3.11(1)-release (i686-pc-linux-gnu)

Câu trả lời:


11

read trả về trạng thái thoát khác không nếu nó không tìm thấy dấu phân cách, luôn luôn là trường hợp khi dấu phân cách là một chuỗi rỗng.


4
Nói đúng ra, đó \0không phải là chuỗi rỗng.
cuonglm

1
@cuonglm, chính xác hơn, khi một đối số trống được truyền đến -d, thì dấu phân cách là \0(một byte NUL). Một trường hợp đặc biệt chủ yếu là kết quả của một tai nạn mã hóa (mặc dù người bashbảo trì sẽ không thừa nhận và giải thích lý do tại sao nó không được ghi lại ).
Stéphane Chazelas 24/2/2016

"... luôn luôn là như vậy khi dấu phân cách là một chuỗi rỗng." Như những người khác đã chỉ ra, thực ra nó \0- và điều đó không phải lúc nào cũng thất bại, bạn có thể sử dụng read -d ''để xử lý đầu ra của find ... -print0, ví dụ.
solidsnack

9

Mã thoát của lệnh đọc là 1 khi đạt đến điểm cuối của tệp (EOF). Điều này sẽ luôn xảy ra khi dấu phân cách -dlà null ''trong trường hợp đặc biệt này trong đó luồng nguồn là một di truyền không thể chứa \ 0.

$ read -d '' message <<-_ThisMessageEnds_
>     this is a
>     multi line
>     message
> _ThisMessageEnds_
$ exitval=$?
$ echo "The exit val was $exitval"
The exit val was 1.

Giá trị thoát đó là một lỗi (không phải 0) làm cho việc thoát tập lệnh có thể tránh được với cấu trúc AND / OR:

read -d '' message <<-_ThisMessageEnds_ || echo "$message"
    this is a
    multi line
    message
_ThisMessageEnds_

Điều đó sẽ gửi tin nhắn đến bàn điều khiển và tránh thoát khỏi nó errexit.

Nhưng khi chúng ta đang trên con đường này để giảm, tại sao không sử dụng trực tiếp:

cat <<-_ThisMessageEnds_
    this is a
    mulitline
    message
_ThisMessageEnds_

Không có lệnh đọc được thực thi (tốc độ cao hơn), không cần biến, không có lỗi từ mã thoát, ít mã để duy trì.


cảm ơn, tốt để biết về việc sử dụng ||để ngăn chặn việc thoát khỏi tập lệnh. Cuối cùng tôi đã loại bỏ errexithoàn toàn kịch bản, tôi gặp vấn đề với nó khi không kích hoạt thoát khi cần - vì vậy nó có vẻ quá khó để có ích. Tôi cũng đã xem xét các mô hình đơn giản hơn với cat <<- EOF message EOFquá. Thật tuyệt khi có thông báo trong một biến có thể được chuyển đến một hàm nếu cần thiết
the_velour_fog 23/2/2016

7
read -d '' message

đọc stdin cho đến khi ký tự đầu tiên không được giải mã (như bạn đã không thêm -r) ký tự NUL hoặc kết thúc đầu vào và lưu trữ dữ liệu sau $IFSvà xử lý dấu gạch chéo ngược vào $message(không có dấu phân cách).

Nếu không tìm thấy dấu phân cách không thoát trong đầu vào, readtrạng thái thoát là khác không. Nó chỉ trả về 0 (thành công) nếu đọc bản ghi đầy đủ, chấm dứt.

Nó hữu ích nhất để xử lý các bản ghi được phân định bằng NUL như đầu ra của find -print0(mặc dù sau đó bạn cần một IFS= read -rd '' recordcú pháp).

Tại đây, bạn cần bao gồm một dấu phân cách NUL trong tài liệu ở đây readđể trở về thành công. Tuy nhiên, điều đó không thể thực hiện với bashcác dải ký tự NUL từ đây - các tài liệu (ít nhất là tốt hơn yashdải đó vượt qua NUL đầu tiên hoặc ksh93 dường như đi vào một vòng lặp vô hạn khi tài liệu ở đây có NUL).

zshlà lớp vỏ duy nhất có thể có NUL trong các tài liệu ở đây hoặc lưu trữ nó trong các biến của nó hoặc chuyển các ký tự NUL trong các đối số cho các hàm / hàm của nó. Trong zsh, bạn có thể làm:

NUL=$'\0'
IFS= read -d $NUL -r var << EOF
1
2
3$NUL
EOF

( zshcũng hiểu read -d ''như một dấu phân cách NUL như bash. read -d $'\0'cũng hoạt động bashnhưng điều đó vượt qua một đối số trống để readthích read -d ''bashkhông hỗ trợ các byte NUL trong dòng lệnh của nó).

(lưu ý rằng sau đó có thêm một ký tự dòng mới $NUL)

Trong bash, bạn có thể sử dụng một ký tự khác:

ONE=$'\1'
IFS= read -d "$ONE" -r var << EOF
1
2
3$ONE
EOF

Nhưng bạn cũng có thể làm:

var=$(cat <<EOF
message
here
EOF
)

Điều đó vẫn sẽ không cho phép các nhân vật NUL. Tuy nhiên, đó là mã tiêu chuẩn, vì vậy bạn không cần phải dựa vào zsh / bash cụ thể read -d. Cũng lưu ý rằng nó sẽ loại bỏ tất cả các ký tự dòng mới, và ngoại trừ ksh93khi catnội dung được bật, điều đó có nghĩa là sinh ra một quy trình và lệnh bổ sung.


Vì vậy, nó có nghĩa là readtrả về giá trị khác không trong trường hợp này vì không tìm thấy dấu phân cách, phải không?
cuonglm

@cuonglm, vâng, đó giống như câu trả lời của bạn, chỉ cần mở rộng một vài điều.
Stéphane Chazelas


5

Khi bạn sử dụng set -o errexitvà tập lệnh của bạn bị hỏng, điều đó có nghĩa là có gì đó không đúng.

Ở đây, nó read, không thể đọc chính xác đầu vào của bạn.

Trong bashkhi bạn sử dụng read -d '', readnội trang sẽ sử dụng ký tự null \0làm dấu kết thúc dòng. Do đó, khi không có \0đầu vào của bạn, readsẽ đọc tất cả đầu vào vào messagebiến và sẽ trả về trạng thái thoát khác không để cho biết có lỗi:

$ while read -d '' line; do echo "$line"; done < <(printf '1')

không in gì trong khi:

$ while read -d '' line; do echo "$line"; done < <(printf '1\0')
1

mang đến cho bạn 1.

readcũng sẽ trả về trạng thái khác không khi đạt đến EOF, nhưng nó được sử dụng để chỉ ra rằng không còn đầu vào để đọc khi bạn sử dụng readvới một whilevòng lặp, do đó whilevòng lặp có thể bị chấm dứt. Nó không liên quan đến vấn đề của bạn.


cảm ơn, các câu trả lời khác giải thích rằng readbiểu hiện của tôi , đã phá vỡ kịch bản. Nhưng đó là một điểm tốt mà đọc cũng thoát ra khác không khi được sử dụng với một vòng lặp read, while. Vì lý do đó, tôi không nghĩ set -o errexitlà đáng tin cậy, vì đôi khi các lệnh cần trả về giá trị khác không như một phần của luồng chương trình thông thường
the_velour_fog 23/2/2016

@the_velour_fog: Thật đáng tin cậy, nếu bạn muốn nó là một phần của luồng chương trình, thì hãy sử dụng điều khiển luồng, nhưif read ...
cuonglm 23/2/2016

Mô tả chính xác là trong việc tìm kiếm một dấu phân cách NUL, EOF được tìm thấy đầu tiên. Những gì nên đọc làm khi tìm EOF: báo cáo lỗi, đó là lỗi.

1
@BinaryZebra, vâng, mặc dù tôi hiểu cuonglm đã sử dụng vòng lặp while để minh họa readtrả về thất bại (và thoát khỏi vòng lặp) khi một dấu phân cách không tìm thấy (mặc dù tôi đồng ý rằng nó có thể chỉ thêm sự nhầm lẫn).
Stéphane Chazelas

1
Về bản chất, tất cả chúng ta đều nói cùng một điều, ít nhiều rõ ràng, không có nhiều điểm để tranh luận thêm về nó.
Stéphane Chazelas 24/2/2016
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.