Bash

在復合命令中設置 bash 選項

  • November 16, 2021

我發現extglob僅在復合化合物中設置 shell 選項會導致後續擴展 glob 失敗。是否需要在復合命令之外設置 shell 選項?我在 Bash 手冊頁中沒有看到這種要求的跡象。

例如,以下腳本可以正常工作(列印a.0 a.1):

#!/bin/bash
touch a.0   a.1 \
     a.b.0 a.b.1
shopt -s extglob
ls "a."!(b*)

但是,如果最後兩行作為複合命令執行,則腳本將失敗並出現以下錯誤:

syntax error near unexpected token `('
`    ls "a."!(b*)'

這是使用從 4.2 到 4.4 的 Bash 版本以及各種複合命令進行測試的:

(1) 有條件的——if

#!/bin/bash
touch a.0   a.1 \
     a.b.0 a.b.1
if true; then
   shopt -s extglob
   ls "a."!(b*)
fi

(2) 大括號——{ }

#!/bin/bash
touch a.0   a.1 \
     a.b.0 a.b.1
{
   shopt -s extglob
   ls "a."!(b*)
}

(3) 子殼—— ( )

#!/bin/bash
touch a.0   a.1 \
     a.b.0 a.b.1
(
   shopt -s extglob
   ls "a."!(b*)
)

在所有情況下,如果將shopt移到復合命令之外,則腳本會成功。

在整個命令被解析之前,複合命令的任何部分都不會執行,這在手冊的“外殼操作”部分中有所記錄:所有標記化和解析都發生在“the”命令執行之前。啟用extglob通過添加新的模式匹配運算符來更改語言語法,這些運算符在標記化過程中被辨識。

因為在到達shopt時該命令尚未執行!(,所以它被視為嘗試的歷史擴展 ( !) 或控制運算符(,而不是!(被視為單個項目(與@(等相同,只是沒有歷史擴展那裡)。

當解析到達該標記時,任何一種情況通常都會出錯,儘管!(...)在行的開頭或之後time是一個否定的子shell管道。" unexpected token (’`" 意味著它不能接受那個地方的子shell表達式。

當您希望僅在子 shell 中臨時啟用 extglob 時,這有點煩人。一種解決方法是定義一個使用所需 glob 的函式,然後重新禁用 extglob,然後從啟用 extglob 的子 shell 呼叫該函式:

shopt -s extglob
f() { ls "a."!(b*) ; }
shopt -u extglob
(
   shopt -s extglob
   f
)

它非常笨重。


如果您在復合命令中創建別名,也會發生相同的效果,因為它們也在解析之前被處理:

if true
then
   alias foo=echo
   foo bar
fi
foo xyz

將列印foo: command not found, 然後xyz,因為別名創建,但在完成之前不可用if

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