Làm thế nào để vượt qua 2> / dev / null dưới dạng một biến?


13

Tôi có mã này hoạt động:

# Hide irrelevant errors so chrome doesn't email us in cron
if [[ $fCron == true ]] ; then
    google-chrome --headless --disable-gpu --dump-dom \
        "$RobWebAddress" > "$DownloadName" 2>/dev/null
else
    # Get silly error messages when running from terminal
    google-chrome --headless --disable-gpu --dump-dom \
        "$RobWebAddress" > "$DownloadName"
fi

Nếu tôi cố gắng rút ngắn nó như thế này:

# Hide irrelevant errors so chrome doesn't email us in cron
local HideErrors
[[ $fCron == true ]] && HideErrors="2>/dev/null"

google-chrome --headless --disable-gpu --dump-dom \
    "$RobWebAddress" > "$DownloadName" "$HideErrors"

Tôi nhận được thông báo lỗi:

[0826/043058.634775:ERROR:headless_shell.cc(597)] Open multiple tabs is only supported when remote debugging is enabled.
[0826/043058.672587:ERROR:headless_shell.cc(597)] Open multiple tabs is only supported when remote debugging is enabled.
[0826/043058.711640:ERROR:headless_shell.cc(597)] Open multiple tabs is only supported when remote debugging is enabled.
(... SNIP ...)

Tại sao một đối số được mã hóa cứng hoạt động nhưng không phải là một đối số như một biến?


Chỉnh sửa 2:

Hiện tại tôi thấy thành công với đề xuất thay thế của câu trả lời thứ hai:

# Redirect errors when cron is used to /dev/null to reduce emails
ErrorPipe=/dev/stderr
[[ $fCron == true ]] && ErrorPipe=/dev/null

google-chrome --headless --disable-gpu --dump-dom \
                "$RobWebAddress" > "$DownloadName" 2>"$ErrorPipe"

Chỉnh sửa 1:

Dựa trên câu trả lời đầu tiên, tôi nên chỉ ra tiêu đề chương trình đã chứa:

[[ $fCron != true ]] &&
    exec 2> >(grep -v 'GtkDialog mapped without a transient parent' >&2)

[[ $fCron == true ]] && exec 2>/dev/nullThay vào đó, bạn có thể thử
Steeldo

.. đại khái, đó là bởi vì shell thiết lập các chuyển hướng trước khi mở rộng các biến, tôi nghĩ vậy. Xem ví dụ bash: Sử dụng một biến để lưu trữ chuyển hướng stderr | stdout
Steeldo

Câu trả lời:


19

Lý do bạn không thể gây ra chuyển hướng xảy ra bằng cách mở rộng "$HideErrors"là các biểu tượng như >không được xử lý đặc biệt sau khi được tạo ra bởi mở rộng tham số . Điều này thực sự rất tốt, bởi vì các biểu tượng như vậy xuất hiện trong văn bản mà bạn có thể muốn mở rộng và sử dụng theo nghĩa đen.

Điều này giữ cho dù bạn có trích dẫn hay không $HideErrors. Kết quả của việc mở rộng tham số là đối tượng tách từglobbing khi mở rộng là không thể viện chứng, nhưng đó là nó.


Đối với những gì phải làm về nó, có rất nhiều cách để đạt được chuyển hướng có điều kiện. Đối với một lệnh rất đơn giản, có thể hợp lý viết toàn bộ lệnh hai lần, một lần trong mỗi nhánh của một casehoặc if- elsexây dựng. Điều này sớm trở nên nặng nề, và lệnh bạn đưa ra chắc chắn là một trường hợp không lý tưởng.

Trong số các cách tiếp cận cho phép bạn tránh lặp lại chính mình , có hai cách tôi đặc biệt khuyên dùng, vì chúng khá sạch sẽ và dễ dàng để có được đúng. Bạn muốn sử dụng chỉ một trong số này, không phải cả hai cùng một lúc cho cùng một lệnh và chuyển hướng.

Lưu lệnh thay vì chuyển hướng. Thay vì cố gắng lưu trữ chuyển hướng trong một biến và áp dụng mở rộng tham số, hãy lưu lệnh trong hàm shell . Sau đó viết một casehoặc if- else, trong đó hàm được gọi với chuyển hướng trên một nhánh và không có nó trên nhánh kia.

Nếu bạn khái niệm lệnh của bạn dưới dạng mã mà bạn muốn viết một lần nhưng chạy trong nhiều trường hợp, thì một hàm là giải pháp tự nhiên. Đây là những gì tôi thường làm. Nó có lợi ích của yêu cầu không phải là một subshell cũng không lưu trữ bằng tay và đặt lại của nhà nước.

Với mã của bạn:

launch() {
    google-chrome --headless --disable-gpu --dump-dom \
        "$RobWebAddress" > "$DownloadName"
}

case $fCron in
true)  launch 2>/dev/null;;
*)     launch;; # Get silly error messages when running from terminal
esac

Bạn có thể áp dụng bất kỳ khoảng cách nào bạn thích, hoặc if- elsethay vào đó nếu bạn thích. Lưu ý rằng launchtự động sử dụng các trình gọi RobWebAddressDownloadNamebiến, ngay cả khi chúng là biến cục bộ, bởi vì Bash có phạm vi động , không giống như hầu hết các ngôn ngữ lập trình có phạm vi từ vựng.

Chạy lệnh trong một lớp con và áp dụng điều kiện chuyển hướng tới exec. Đây là những gì Steeldo bình luận , nhưng bên trong ( )để giữ hiệu ứng cục bộ . Khi các execBUILTIN đang chạy không có đối số, nó không thay thế vỏ hiện tại với một quá trình mới, nhưng thay vào đó áp dụng bất kỳ chuyển hướng của nó vào vỏ hiện hành.

(Cũng có thể theo dõi lỗi tiêu chuẩn là gì và khôi phục nó, mà không cần sử dụng một lớp con và do đó không làm mất khả năng sửa đổi môi trường của trình bao hiện tại.

Với mã của bạn:

(
    # Suppress silly error messages unless running from terminal
    case $fCron in true) exec 2>/dev/null;; esac

    google-chrome --headless --disable-gpu --dump-dom \
        "$RobWebAddress" > "$DownloadName"
)

Sau khi đóng ), lỗi tiêu chuẩn có hiệu lực được khôi phục về bất cứ điều gì trước đây, bởi vì nó chỉ thực sự được chuyển hướng trong lớp con chứ không phải trong vỏ cha. Điều này cũng vậy, hoạt động tốt với các biến shell hiện có, vì các subshells có được một bản sao của các biến đó. Mặc dù tôi thích sử dụng hàm shell, tôi thừa nhận phương pháp này có thể yêu cầu ít mã hơn.

Cả hai phương pháp đều hoạt động bất kể lỗi tiêu chuẩn của tệp hoặc thiết bị bắt đầu như thế nào, kể cả trong trường hợp chuyển hướng được áp dụng cho các hàm shell gọi mã chứa hành vi có điều kiện, cũng như trường hợp (được đề cập trong chỉnh sửa của bạn) trong đó lỗi tiêu chuẩn cho toàn bộ tập lệnh đã được chuyển hướng bởi trước đó hoặc . Đường dẫn được tạo ra bởi quá trình thay thế là không có vấn đề.exec 2>&fdexec 2> path


FYI SteelDriver đã đề cập một vài điều về execviệc không biết liệu anh ta có kế hoạch trả lời về điều đó không ...
WinEunuuchs2Unix

@ WinEunuuchs2Unix Tôi hy vọng câu trả lời như vậy vẫn được đăng. Mặc dù tôi chủ yếu khuyên bạn nên sử dụng một hàm, tôi cũng bao gồm một phương thức liên quan đến chuyển hướng trên exec. Nhưng, như tôi đã đề cập trong ngoặc đơn, tôi đã không bao gồm các ứng dụng phức tạp hơn trong đó bộ mô tả tệp cũ được lưu giữ và khôi phục mà không có phần phụ. Tôi cũng không bao gồm các ứng dụng ít phức tạp hơn, như chỉ giữ lại chuyển hướng nếu đây là phần cuối của tập lệnh. Một câu trả lời khác, nếu được đăng, có thể bao gồm cả hai, và có lẽ nhiều hơn nữa.
Eliah Kagan

Tôi đã cập nhật câu hỏi của mình với câu hỏi hiện tại execkhông ảnh hưởng đến câu trả lời của bạn.
WinEunuuchs2Unix

@ WinEunuuchs2Unix Vâng, điều đó sẽ không có vấn đề gì. Tôi đã thêm một đoạn về nó vào cuối câu trả lời.
Eliah Kagan

Tiết lộ thú vị khi đọc câu trả lời của bạn, RobWebAddresschắc chắn là bối cảnh toàn cầu. DownloadNameđã được xác định địa phương nhưng nên là bối cảnh toàn cầu. Vì một số lý do, các hàm con kế thừa các định nghĩa cục bộ của cha mẹ ( DownloadNamecó thể nhìn thấy DownloadAsHTML ()hàm được gọi bởi UpdateOne ()hàm đã định nghĩa nó là cục bộ. Đó là một ngày khó khăn :(
WinEunuuchs2Unix

4

Tại sao một đối số được mã hóa cứng hoạt động nhưng không phải là một đối số như một biến?

Bởi vì các mục cú pháp không được giải thích từ các giá trị biến mở rộng. Đó là, mở rộng biến không giống như thay thế tham chiếu biến bằng văn bản của biến trong dòng lệnh. (Stuff như ;, |, &&và dấu ngoặc kép vv cũng không phải là đặc biệt trong các giá trị của biến.)

Những gì bạn có thể làm là sử dụng các bí danh hoặc sử dụng biến để chỉ giữ mục tiêu của chuyển hướng.

Bí danh chỉ một sự thay thế văn bản, vì vậy họ có thể giữ mục cú pháp, giống như các nhà khai thác và từ khóa. Trong một tập lệnh, shopt expand_aliasestheo mặc định , bạn cần phải tắt chúng trong các shell không tương tác. Vì vậy, bản in này 2(chỉ):

#!/bin/bash
shopt -s expand_aliases

alias redir='> /dev/null'
redir echo 1
alias redir=''
redir echo 2

(Và bạn cũng có thể alias jos=if niin=then soj=fivà sau đó viết tất cả các câu lệnh if của mình bằng tiếng Phần Lan. Tôi chắc rằng bất kỳ ai đọc kịch bản cũng sẽ yêu bạn.)

Hoặc, luôn luôn viết chuyển hướng, nhưng chỉ kiểm soát mục tiêu bằng một biến. Bạn sẽ cần một mục tiêu không hoạt động trong trường hợp bạn không muốn thay đổi nơi đầu ra đi, nhưng /dev/stderrsẽ hoạt động trong trường hợp đó. Trên thực tế, việc thêm 2> /dev/stderrkhông phải là không có vì cách Linux đối xử với fd được mở từ /proc/<pid>/fdđộc lập với bản gốc. Điều này ảnh hưởng đến vị trí của vị trí ghi và sẽ làm rối đầu ra nếu nó đi đến một tệp thông thường.

Nó sẽ hoạt động ở chế độ chắp thêm, mặc dù (hoặc nếu stderr đi đến một đường ống hoặc đến một thiết bị đầu cuối):

#!/bin/sh
exec 2>/tmp/error.log
dst=/dev/null
ls -l /nosuchfile-1 2>> "$dst"     # this doesn't print
dst=/dev/stderr
ls -l /nosuchfile-2 2>> "$dst"
ls -l /nosuchfile-3 2>> "$dst"

Vì vậy, để lặp lại: 2> /dev/stderrcó thể phá vỡ.


Hahaha, từ giờ trở đi tôi sẽ chỉ sử dụng ifs tại Phần Lan. :>
tráng miệng

Tôi thích đề nghị thay thế. Ý nghĩ expand_aliaseslà đáng sợ bởi vì ~/.bashrctôi nghĩ chương trình của bạn có thể bị bắt làm con tin .
WinEunuuchs2Unix

1
@ WinEunuuchs2Unix, vâng, expand_aliaseshơi đáng sợ. Nhưng ~/.bashrckhông nên là một vấn đề vì nó chỉ được đọc bởi các shell tương tác .profilevà những người bạn có thể gọi nó chỉ được đọc bởi các shell đăng nhập. Các shell không đăng nhập không tương tác như các script không nên chạy bất kỳ shell nào. (Tuy nhiên, sau đó có $BASH_ENV, và dường như .bashrcđang đọc nếu stdin được kết nối với một ổ cắm mạng như thế nào phức tạp nó có thể nhận được ....)
ilkkachu

Vâng, tôi hiểu đề xuất thay thế của bạn một cách hoàn hảo và tôi sẽ thử nó tối nay :)
WinEunuuchs2Unix

Thành thật mà nói, tôi không chắc chắn cách nào tôi sẽ thực hiện điều này nếu tôi phải làm. Tôi có thể lưu trữ lệnh trong một hàm hoặc một mảng và sau đó phân nhánh để quyết định xem có nên chuyển hướng ở đó không (sử dụng một hàm được hiển thị trong câu trả lời khác). Hoặc 2>> "$dst"mánh khóe đó , nhưng tôi chỉ nhận ra nó không hoạt động trong trường hợp chung, vì vậy tốt hơn hãy cẩn thận với nó.
ilkkachu

1

Tiêu đề câu hỏi: "Làm thế nào để vượt qua 2> / dev / null dưới dạng một biến?" Điều này thực sự có thể được thực hiện bằng cách sử dụngeval

joshua@nova:/tmp$ X=">/dev/null"
joshua@nova:/tmp$ echo $X
>/dev/null
joshua@nova:/tmp$ eval echo $X
joshua@nova:/tmp$ eval echo hi
hi
joshua@nova:/tmp$ eval echo hi $X
joshua@nova:/tmp$ echo hi $X
hi >/dev/null
joshua@nova:/tmp$ 

Vì vậy, chúng ta có thể viết lại như

# Hide irrelevant errors so chrome doesn't email us in cron
local HideErrors
local RobWebAddress2
local DownloadName2
[[ $fCron == true ]] && HideErrors="2>/dev/null"
RobWebAddress2='"$RobWebAddress"'
DownloadName2='>"$DownloadName"'

eval google-chrome --headless --disable-gpu --dump-dom \
    $RobWebAddress2 $DownloadName2 "$HideErrors"

Trường hợp truy cập biến gián tiếp ngăn cản sự mở rộng xảy ra quá sớm trên phần còn lại của dòng lệnh.

Dấu ngoặc kép trong các biến chỉ hoạt động tốt.

joshua@nova:/tmp$ X='"'
joshua@nova:/tmp$ Y='$X'
joshua@nova:/tmp$ eval echo $Y
"
joshua@nova:/tmp$ 

@EliahKagan: Tiêu đề câu hỏi: "Làm thế nào để vượt qua 2> / dev / null làm biến?"
Joshua

Ok nó không hoạt động. Tôi đã sưa nó.
Joshua

Bây giờ tệp luôn được đặt tên DownloadName- và văn bản bằng chữ RobWebAddressluôn được sử dụng cho URL. Bạn đang sử dụng $" "trích dẫn . Tôi nghĩ rằng điều này có thể là vô ý và bạn có thể muốn những thứ $bên trong " ", nhưng bạn đã làm theo cách đó ở cả hai nơi, vì vậy tôi không chắc chắn. Tôi nghĩ chỉ > "$DownloadName"nên sửa nó. Nhưng tôi hiểu bạn có thể không thích điều đó, vì vô tình trộn lẫn các đối số và không tranh luận với nhau evallà một lý do khiến nó rất nguy hiểm và không được khuyến khích sử dụng evalhành vi kết hợp của nó.
Eliah Kagan

@EliahKagan: Ồ. Shell ưa thích của tôi cho kịch bản không có trích dẫn $ "".
Joshua

1
Nếu bạn sửa nó, nó sẽ hoạt động. Và tôi đã luôn luôn sai khi nghĩ rằng nó dán trích dẫn trên văn bản tùy ý! Nó xây dựng các lập luận theo nghĩa đenevalnối lại trước khi đánh giá. Nhưng tôi nghĩ một cách khác để nói rằng đó là một cách khó hiểu để viết eval 'google-chrome --headless --disable-gpu --dump-dom "$RobWebAddress" > "$DownloadName" '"$HideErrors"giống với sự xuất hiện của mã OP. Và nói chung, sử dụng evalcho các nhiệm vụ không cần nó là xấu . (Không có lý do này - thậm chí cũng không giải thích - sai quấy và thù địch trả lời cũ của tôi.)
Eliah Kagan
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.