Bash

將 stdout 和 stderr 重定向到 ‘&&’ 鏈中間的 null

  • November 19, 2020

TL;博士

我對此進行了研究,您可以在“更多詳細資訊和我的嘗試”部分中查看我發現的內容。

我的命令是

date && echo "hi 1" && echo "1/0" | bc >/dev/null 2>&1 && echo "hi 2" && date +'%s'

我期望/想要的輸出類似於

Tue, Nov 17, 2020  3:13:49 PM
hi 1

讓它停下來。我的想法是表達式的“ echo/ bc”部分(括號表示假定的操作優先級)

<part-before> ( echo "1/0" | bc >/dev/null 2>&1 ) && <part-after>

應該做到這一點,這樣<part-after>就不會達到。但是,我的實際輸出(date現在的命令)是

Tue, Nov 17, 2020  3:13:49 PM
hi 1
hi 2
1605651229

問題:我怎樣才能讓它失敗並停止在被零除的問題上而不輸出任何東西?


(在此之後的任何內容都是為了提供更多詳細資訊。)

快速說明:即使沒有重定向,除零失敗也不會停止&&鏈,即短路-‘和’-運算符鏈。看哪,

bballdave025@MACHINE ~
$ date && echo "hi 1" && echo "1/0" | bc && echo "hi 2" && date +'%s'
Tue, Nov 17, 2020  3:14:47 PM
hi 1
Runtime error (func=(main), adr=3): Divide by zero
hi 2
1605651287

bballdave025@MACHINE ~
$

我想使用該bc方法,主要是為了避免隨後出現的 bash-isms $(( ))。(說到 bash-isms,嘗試&>/dev/null並沒有改變任何東西。)我想最終得到類似以下範例的內容

bballdave025@MACHINE ~
$ date && echo "hi 1" && echo "$((1/0))" >/dev/null 2>&1 && echo "hi 2" && date +'%s'
Tue, Nov 17, 2020  3:15:17 PM
hi 1
-bash: 1/0: division by 0 (error token is "0")

bballdave025@MACHINE ~
$

除了我不希望出現錯誤。只要不輸出除以零的錯誤結果,即不是echo-ed (這違背了重定向的目的stdout) ,我就可以讓錯誤消失,stderr就像這樣

### NOT WHAT I WANT, other than what is output ###
bballdave025@MACHINE ~
$ date && echo "hi 1" && ((1/0)) >/dev/null 2>&1 && echo "hi 2" && date +'%s'
Tue, Nov 17, 2020  3:15:44 PM
hi 1

bballdave025@MACHINE ~
### NOT WHAT I WANT, other than what is output. ###

在更理論上的說明上:有些東西正在將 0(零)傳遞給&&; 它是什麼?

我已經找到了這個來源已歸檔),它指出

重定向不是命令。

我現在不知道該去哪裡找。


編輯:我做了更多的研究。我想補充兩點。

  1. 我找到了幾個來源。其中一個已歸檔)我認為可能是關鍵,但我仍然不明白它是如何應用的。另一個歸檔)描述了短路,我認為這可能很重要。
  2. 當我不小心替換為 時,我發現了一個奇怪的行為>/dev/null,因此在除零問題之後>dev/null引入了一個錯誤。這是我看到的,
bballdave025@MACHINE ~
$ a=1; b=1; c=0; date && echo "hi 1" && echo "$a/$b" | bc >dev/null 2>&1 && echo "hi 2" && date +'%s'
Tue, Nov 17, 2020  3:41:39 PM
hi 1
-bash: dev/null: No such file or directory

bballdave025@MACHINE ~
$ a=1; b=1; c=0; date && echo "hi 1" && echo "$a/$c" | bc >dev/null 2>&1 && echo "hi 2" && date +'%s'
Tue, Nov 17, 2020  3:41:48 PM
hi 1
-bash: dev/null: No such file or directory

bballdave025@MACHINE ~
$

老實說,這讓我更加困惑,特別是考慮到第一個可能是關鍵來源。


更多細節和我的嘗試

我試圖了解&&鍊和重定向,/dev/null以建立一個包含更大執行檔的更長鏈。我也想更好地理解命令列表分隔符歸檔),一般來說,無論這些執行檔如何使用&&,我真的很想了解這裡發生了什麼。

在繼續我的真實範例之前,我一直在研究這個玩具範例。我一直在嘗試“倒退”,看看是否能找到問題所在。也許這對弄清楚事情很有用。

bballdave025@MACHINE ~
$ a=1; b=1; c=0; date && echo "hi 1" && echo "$a/$b" | bc >/dev/null 2>&1 && echo "hi 2" && date +'%s'
Tue, Nov 17, 2020  3:16:41 PM
hi 1
hi 2
1605651401

bballdave025@MACHINE ~
$ a=1; b=1; c=0; date && echo "hi 1" && echo "$a/$c" | bc >/dev/null 2>&1 && echo "hi 2" && date +'%s'
Tue, Nov 17, 2020  3:16:52 PM
hi 1
hi 2
1605651412

## LET'S SEE WHAT GETS SENT TO /dev/null

bballdave025@MACHINE ~
$ a=1; b=1; c=0; date && echo "hi 1" && echo "$a/$b" | bc >abc 2>&1 && echo "hi 2" && date +'%s'
Tue, Nov 17, 2020  3:17:06 PM
hi 1
hi 2
1605651426

bballdave025@MACHINE ~
$ cat abc
1

bballdave025@MACHINE ~
$ a=1; b=1; c=0; date && echo "hi 1" && echo "$a/$c" | bc >abc 2>&1 && echo "hi 2" && date +'%s'
Tue, Nov 17, 2020  3:17:18 PM
hi 1
hi 2
1605651438

bballdave025@MACHINE ~
$ cat abc
Runtime error (func=(main), adr=3): Divide by zero

## LET'S TRY GIVING IT ANOTHER ERROR RIGHT AFTER

bballdave025@MACHINE ~
$ a=1; b=1; c=0; date && echo "hi 1" && echo "$a/$b" | bc >dev/null 2>&1 && echo "hi 2" && date +'%s'
Tue, Nov 17, 2020  3:24:35 PM
hi 1
-bash: dev/null: No such file or directory

bballdave025@MACHINE ~
$ a=1; b=1; c=0; date && echo "hi 1" && echo "$a/$c" | bc >dev/null 2>&1 && echo "hi 2" && date +'%s'
Tue, Nov 17, 2020  3:25:00 PM
hi 1
-bash: dev/null: No such file or directory

bballdave025@MACHINE ~
$ a=1; b=1; c=0; date && echo "hi 1" && echo "$a/$b" | bc >abc 2>&1 && echo "hi 2" && date +'%s'
Tue, Nov 17, 2020  3:25:14 PM
hi 1
hi 2
1605651914

bballdave025@MACHINE ~
$ cat abc
1

bballdave025@MACHINE ~
$ a=1; b=1; c=0; date && echo "hi 1" && echo "$a/$c" | bc >abc 2>&1 && echo "hi 2" && date +'%s'
Tue, Nov 17, 2020  3:25:24 PM
hi 1
hi 2
1605651924

bballdave025@MACHINE ~
$ cat abc
Runtime error (func=(main), adr=3): Divide by zero

bballdave025@MACHINE ~
$ a=1; b=1; c=0; date && echo "hi 1" && echo "$a/$b" | bc >abc/def 2>&1 && echo "hi 2" && date +'%s'
Tue, Nov 17, 2020  3:25:41 PM
hi 1
-bash: abc/def: Not a directory

bballdave025@MACHINE ~
$ a=1; b=1; c=0; date && echo "hi 1" && echo "$a/$c" | bc >abc/def 2>&1 && echo "hi 2" && date +'%s'
Tue, Nov 17, 2020  3:25:46 PM
hi 1
-bash: abc/def: Not a directory

## GO BACK WITHOUT `bc` and without redirection

bballdave025@MACHINE ~
$ a=1; b=1; c=0; date && echo "hi 1" && echo "$a/$b" >/dev/null 2>&1 && echo "hi 2" && date +'%s'
Tue, Nov 17, 2020  3:29:03 PM
hi 1
hi 2
1605652143

bballdave025@MACHINE ~
$ a=1; b=1; c=0; date && echo "hi 1" && echo "$a/$c" >/dev/null 2>&1 && echo "hi 2" && date +'%s'
Tue, Nov 17, 2020  3:29:12 PM
hi 1
hi 2
1605652152

bballdave025@MACHINE ~
$ a=1; b=1; c=0; date && echo "hi 1" && echo "$a/$b" >abc 2>&1 && echo "hi 2" && date +'%s'
Tue, Nov 17, 2020  3:29:21 PM
hi 1
hi 2
1605652161

bballdave025@MACHINE ~
$ cat abc
1/1

bballdave025@MACHINE ~
$ a=1; b=1; c=0; date && echo "hi 1" && echo "$a/$c" >abc 2>&1 && echo "hi 2" && date +'%s'
Tue, Nov 17, 2020  3:29:29 PM
hi 1
hi 2
1605652169

bballdave025@MACHINE ~
$ cat abc
1/0

bballdave025@MACHINE ~
$ a=1; b=1; c=0; date && echo "hi 1" && echo "$a/$b" && echo "hi 2" && date +'%s'
Tue, Nov 17, 2020  3:30:09 PM
hi 1
1/1
hi 2
1605652209

bballdave025@MACHINE ~
$ a=1; b=1; c=0; date && echo "hi 1" && echo "$a/$c" && echo "hi 2" && date +'%s'
Tue, Nov 17, 2020  3:30:14 PM
hi 1
1/0
hi 2
1605652214

bballdave025@MACHINE ~
$ a=1; b=1; c=0; date && echo "hi 1" && echo "hi 2" && date +'%s'
Tue, Nov 17, 2020  3:30:26 PM
hi 1
hi 2
1605652226

bballdave025@MACHINE ~
$

這個 SO 交換歸檔)可能有一些有用的東西。

這是一個混合了 XY 問題、關於管道、輸出和劃分的問題。我會嘗試澄清它們,儘管我覺得沒有一個“單一”的問題需要回答。

外殼語言

分組

echo "before" && echo inside1 && echo inside2 && echo "after"

對多個語句進行分組的方法是使用圓括號或大括號:

echo "before" && ( echo inside1 && echo inside2 ) && echo "after"
echo "before" && { echo inside1 && echo inside2; } && echo "after"

()將在子shell中執行,而{ }只是在目前環境中形成一個列表。兩者都可用於重定向該部分的輸出。

echo "before" && ( echo inside1 && echo inside2 ) > /dev/null && echo "after"
echo "before" && { echo inside1 && echo inside2; } > /dev/null && echo "after"

返回值

每個命令在完成時都會返回一個值。它保存在上面,因此您可以在執行後立即$?查看它。echo $?

一致認為返回零表示成功,非零表示失敗。無論該計劃可能認為是成功的。and&&和 or||外殼運算符使用此概念。還有幾個方便的小工具被呼叫truefalse它們總是分別返回零(成功)和非零(失敗)。

if true && false; then
echo Yes
else
echo No
fi

當您連結一系列 時&&,僅當左手返回成功時才執行運算符的右手。

例如,您可能想要創建一個名為的文件夾並將photos所有文件移到那裡。

你可以先試試

mv *.jpg photos

但如果只有一張照片,沒有photos文件夾,它會將照片重命名為photos.

如果文件夾創建成功,您可以使用&&僅移動圖像:

mkdir photos && mv *.jpg photos

如果mkdir失敗,mv則不執行。

之所以如此,是因為失敗mkdir時將返回非零。

但是,如果文件夾photos已經存在,mkdir也會返回錯誤條件(無法創建文件夾)。

所以更複雜的檢查可能是:

( test -d photos || mkdir photos ) && mv *.jpg photos/

如果 photos 已經是一個目錄,它不會嘗試創建它,而是會跳轉到移動圖像。如果photos不是目錄,但它能夠製作它,它也會成功並移動照片。

如果照片不是文件夾(或指向文件夾的符號連結)且未創建,則左側部分&&將失敗並且照片不會移動。

旁注:對於這個特定的範例,您可以使用更簡單的mkdir -p photos && mv *.jpg photos/,因為-pflag 使 mkdir 創建中間文件夾,並且如果該文件夾已經存在,則不會退出,但它不那麼具有指導意義。

管道

當你有一個管道時,預設情況下它的返回值是最右邊的命令之一。如果你執行

command1 | command2 | command3

整個管道的返回值將是command3. command1command2返回非零無關緊要 。這可以通過設置選項來更改,pipefail以便返回值將是管道中返回 non-zero 的最右邊的命令之一

被零除

在您的情況下,echo 1/0 | bc不會停止管道,因為bc它確實返回了 0 程式碼,即使它在螢幕上提供了關於嘗試除以零的診斷。

直接做$((1/0))是有問題的,因為錯誤發生在擴展上。您可以將其包裝在一個新的 shell 程序中:

echo hi1 && bash -c 'echo $((1/0))' 2> /dev/null && echo hi2

甚至只是一個子shell

echo hi1 && ( echo $((1/0))' ) 2> /dev/null && echo hi2

還有實際檢查除數是否為零的基本解決方案。例如:

division() {
 if [ "$2" -eq 0 ]; then
    return 1
 fi
 echo "$(( $1 / $2 ))"
}

echo hi1 && division 1 0 && echo hi2

你甚至可以用一個函式做類似的事情bc(雖然它會輸出一個 0 而不是空的):

define division(x,y) {
  if (y == 0) return;
  return x/y;
}
division(1,0)

即使沒有重定向,除零失敗也不會停止 && 鏈,即短路的“與”運算符鏈。

GNU 公元前:

$ echo "1/0" | bc 
Runtime error (func=(main), adr=3): Divide by zero
$ echo $?
0

忙箱:

$ echo "1/0" | busybox bc 
bc: divide by zero
$ echo $?
1

POSIX 公元前

名稱

bc - 任意精度算術語言

EXIT STATUS

應返回以下退出值:

0 - 所有輸入文件均已成功處理。

未指定——發生錯誤。

基本原理

錯誤條件的退出狀態未指定有幾個原因:

  • bc 實用程序用於互動式和非互動式情況。不同的退出程式碼可能適用於這兩種用途。
  • 目前尚不清楚何時應該給出非零退出;被零除、未定義的函式和語法錯誤都是可能的。
  • 目前尚不清楚退出狀態具有什麼實用程序

它不會破壞鏈條,因為您的 bc 不認為除以零是值得返回虛假退出狀態的東西。

但是,如果 GNU bc 無法讀取輸入文件,它確實會返回一個虛假的退出狀態。

我想使用 bc 方法,主要是為了避免像 $(( )) 這樣的 bash 主義。

$(( ))不是 Bashism,它是POSIX shell 語言(算術擴展)的一部分。


我不確定這裡的上下文是什麼,除以零與條件和&&鏈的關係,以及你最終要做什麼,所以很難提供任何建議。

依賴於檢測除以零的替代方法是在首先嘗試除法之前檢查零除數。

引用自:https://unix.stackexchange.com/questions/620179