来自man bash
:
$ {参数:字}
如果参数为null或未设置,则单词的扩展(或者如果单词不存在则为该效果的消息)写入标准错误,并且shell(如果不是交互式)退出。否则,参数的值将被替换。
那么为什么我不能将错误重定向到/dev/null
(或其他任何地方):
$ ${a:?} 2> /dev/null
bash: a: parameter null or not set
echo
:
$ echo ${a:?} 2> /dev/null
bash: a: parameter null or not set
$ echo Hello world ${a:?} 2> /dev/null
bash: a: parameter null or not set
我的bash版本是4.3.42 。
为了不转移我的问题,我需要说a
没有定义,它永远不会,所以我的问题在定义时并不适用,因为我不在乎如果a
扩展为不是命令,别名等的东西......
考虑这个例子不要混淆:
$ echo ${a:=1} ${b:?} ${c:=3}
bash: b: parameter null or not set
$ echo "a: $a - c: $c"
a: 1 - c:
答案 0 :(得分:1)
了解shell解释命令的顺序非常重要。
这由Posix standard, volume XSH, section 2.1指定。 (各种shell,包括bash,都有这个过程的扩展,但基础知识没有改变):
- shell从文件(参见sh),-c选项或从POSIX.1-2008的系统接口卷中定义的system()和popen()函数读取其输入。如果shell命令文件的第一行以字符"#!"开头,则结果未指定。
- shell将输入分解为标记:单词和运算符;见令牌识别。
- shell将输入解析为简单命令(请参阅简单命令)和复合命令(请参阅复合命令)。
- shell对每个命令的不同部分执行各种扩展(单独),从而产生一个路径名和字段列表,将其视为命令和参数;见wordexp。
- shell执行重定向(请参阅重定向)并从参数列表中删除重定向运算符及其操作数。
- shell执行一个函数(参见函数定义命令),内置(参见特殊内置实用程序),可执行文件或脚本,给出参数名称作为编号为1到n的位置参数,以及名称命令(或脚本中的函数,脚本的名称)作为编号为0的位置参数(参见命令搜索和执行)。
- shell可选择等待命令完成并收集退出状态(请参阅命令的退出状态)。
醇>
这里,相关部分是步骤4到6.当我们到达第4步时,shell已经确定了它将要运行的命令。然后它执行指示的各种扩展,包括${a:?}
。此时,我们尚未开始执行命令,也没有进行任何重定向。
在步骤5中,执行重定向并从命令行中删除。 (从左到右)。
在步骤6中,识别命令名称(因为它可能取决于步骤4中的扩展),该名称被标识为特殊内置,别名或外部程序 - 在这种情况下,找到可执行文件 - 最后在步骤5中执行重定向的环境中运行该命令。
因此,重定向stderr不适用于参数扩展。参数扩展期间生成的任何错误消息都将发送到shell环境的当前标准错误。
构造{ command-list }
是复合命令。如Posix XSH section 2.9.4中所示:(突出显示已添加):
shell有几个编程结构,它们是"复合命令",它们为命令提供控制流。这些复合命令中的每一个在开头都有一个保留字或控制操作符,在结尾处有一个相应的终结符保留字或操作符。此外,每个都可以在与终结符相同的行上进行重定向。每个重定向应适用于复合命令中未明确覆盖该重定向的所有命令。
因此,应用于复合命令的重定向适用于复合命令的持续时间,因此在执行复合命令期间包括shell 的输出。
值得注意的是:
${a:=1} ${b:?} ${c:=3}
不正确。如果$b
有价值 - 说" B" - ,以便命令的执行没有中断,那么要执行的命令将是1
,因为扩展的结果将是:
1 B 3
(假设默认赋值构造为a
和c
分配了值)。这将尝试运行命令1
,为其提供参数B
和3
。由于在步骤5中应用重定向后发生了这种情况,因此错误消息(1: not found
)将被重定向,这意味着如果您已经重新定向,则无法看到它将stderr重定向到/dev/null
。
如果要使用条件扩展执行一个或多个参数设置,则应使用:
(无操作)命令:
: ${a:=1} ${b:?} ${c:=3}
最后,参数扩展期间的错误可能会中断命令执行是正确的。如果命令行出现在脚本中(以便shell不是交互式的),那么${b:?}
的失败会导致shell以非零退出状态退出" (来自Posix XSH section 2.6.2,以及OP中引用的bash手册),这将使所有变量设置无效,因为它们是执行shell的本地变量。 XSH 2.6.2继续说"交互式shell不需要退出",事实上,bash
和dash
交互式shell都不会退出。但错误是错误,它会中断命令执行。这应该是这种情况由Posix XSH section 2.8.2建议:
如果命令在字扩展或重定向期间失败,则其退出状态应大于零。
虽然没有明确说明命令执行因错误而中断,但似乎是一个明确的指示,因为错误已经设置了命令的退出状态。当然,我试过的贝壳也是如此。
答案 1 :(得分:0)
似乎bash
除了打印到标准错误外,如果未设置变量,会丢弃当前命令。
示例(请参阅c
未分配):
$ ${a:=1} ${b:?} ${c:=3}
bash: b: parameter null or not set
$ echo "a: $a - c: $c"
a: 1 - c:
那么为什么不重定向错误?:
$ ${d:?} 2> /dev/null
bash: d: parameter null or not set
这是因为2> /dev/null
永远不会执行,因为参数扩展会发生fisrt并在这种情况下丢弃命令的其余部分。
我们可以看到bash
源代码中发生了什么
函数调用堆栈(片段):
static WORD_LIST *
shell_expand_word_list (tlist, eflags)
WORD_LIST *tlist;
int eflags;
{
...
expanded = expand_word_internal // LOOK!
(tlist->word, 0, 0, &has_dollar_at, &expanded_something);
if (expanded == &expand_word_error || expanded == &expand_word_fatal)
{
/* By convention, each time this error is returned,
tlist->word->word has already been freed. */
tlist->word->word = (char *)NULL;
/* Dispose our copy of the original list. */
dispose_words (orig_list);
/* Dispose the new list we're building. */
dispose_words (new_list);
last_command_exit_value = EXECUTION_FAILURE;
if (expanded == &expand_word_error)
exp_jump_to_top_level (DISCARD); // LOOK!
else
exp_jump_to_top_level (FORCE_EOF);
}
...
}
#define DISCARD 2 /* Discard current command. */
static WORD_LIST *
expand_word_internal(word, quoted, isexp, contains_dollar_at, expanded_something)
WORD_DESC *word;
int quoted, isexp;
int *contains_dollar_at;
int *expanded_something;
{
...
case '$':
...
tword = param_expand (string, &sindex, quoted, expanded_something, // LOOK!
&temp_has_dollar_at, "ed_dollar_at,
&had_quoted_null, pflags);
has_dollar_at += temp_has_dollar_at;
split_on_spaces += (tword->flags & W_SPLITSPACE);
if (tword == &expand_wdesc_error || tword == &expand_wdesc_fatal)
{
free (string);
free (istring);
return ((tword == &expand_wdesc_error) ? &expand_word_error
: &expand_word_fatal);
}
...
}
/* Expand a single ${xxx} expansion. The braces are optional. When
the braces are used, parameter_brace_expand() does the work,
possibly calling param_expand recursively. */
static WORD_DESC *
param_expand (string, sindex, quoted, expanded_something,
contains_dollar_at, quoted_dollar_at_p, had_quoted_null_p,
pflags)
char *string;
int *sindex, quoted, *expanded_something, *contains_dollar_at;
int *quoted_dollar_at_p, *had_quoted_null_p, pflags;
{
...
case LBRACE:
tdesc = parameter_brace_expand (string, &zindex, quoted, pflags, // LOOK!
quoted_dollar_at_p,
contains_dollar_at);
if (tdesc == &expand_wdesc_error || tdesc == &expand_wdesc_fatal)
return (tdesc);
...
}
*`LBRACE` means what follows the dollar sign*
/* ${[#][!]name[[:][^[^]][,[,]]#[#]%[%]-=?+[word][:e1[:e2]]]} */
static WORD_DESC *
parameter_brace_expand (string, indexp, quoted, pflags, quoted_dollar_atp,
contains_dollar_at)
char *string;
int *indexp, quoted, *quoted_dollar_atp, *contains_dollar_at, pflags;
{
...
else if (c == '?')
{
parameter_brace_expand_error (name, value); // LOOK!
return (interactive_shell ? &expand_wdesc_error : &expand_wdesc_fatal);
}
...
}
/* Deal with the right hand side of a ${name:?value} expansion in the case
that NAME is null or not set. If VALUE is non-null it is expanded and
used as the error message to print, otherwise a standard message is
printed. */
static void
parameter_brace_expand_error (name, value)
char *name, *value;
{
WORD_LIST *l;
char *temp;
last_command_exit_value = EXECUTION_FAILURE; /* ensure it's non-zero */
if (value && *value)
{
l = expand_string (value, 0);
temp = string_list (l);
report_error ("%s: %s", name, temp ? temp : ""); /* XXX was value not ""*/
FREE (temp);
dispose_words (l);
}
else
report_error (_("%s: parameter null or not set"), name); // LOOK!
/* Free the data we have allocated during this expansion, since we
are about to longjmp out. */
free (name);
FREE (value);
}
report_error (format, arg1, arg2)
char *format, *arg1, *arg2;
{
fprintf (stderr, format, arg1, arg2);
fprintf (stderr, "\n");
}