重定向$ {var:?}构造中的标准错误

时间:2015-09-27 13:59:12

标签: bash io-redirection

来自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: 

2 个答案:

答案 0 :(得分:1)

了解shell解释命令的顺序非常重要。

这由Posix standard, volume XSH, section 2.1指定。 (各种shell,包括bash,都有这个过程的扩展,但基础知识没有改变):

  1. shell从文件(参见sh),-c选项或从POSIX.1-2008的系统接口卷中定义的system()和popen()函数读取其输入。如果shell命令文件的第一行以字符"#!"开头,则结果未指定。
  2. shell将输入分解为标记:单词和运算符;见令牌识别。
  3. shell将输入解析为简单命令(请参阅简单命令)和复合命令(请参阅复合命令)。
  4. shell对每个命令的不同部分执行各种扩展(单独),从而产生一个路径名和字段列表,将其视为命令和参数;见wordexp。
  5. shell执行重定向(请参阅重定向)并从参数列表中删除重定向运算符及其操作数。
  6. shell执行一个函数(参见函数定义命令),内置(参见特殊内置实用程序),可执行文件或脚本,给出参数名称作为编号为1到n的位置参数,以及名称命令(或脚本中的函数,脚本的名称)作为编号为0的位置参数(参见命令搜索和执行)。
  7. 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

(假设默认赋值构造为ac分配了值)。这将尝试运行命令1,为其提供参数B3。由于在步骤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不需要退出",事实上,bashdash交互式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, &quoted_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");
}