【问题标题】:What are the rules for valid identifiers (e.g. functions, vars, etc) in Bash?Bash 中有效标识符(例如函数、变量等)的规则是什么?
【发布时间】:2015-03-22 19:01:43
【问题描述】:

Bash 中标识符的语法规则,尤其是函数名和变量名?

我编写了一个 Bash 脚本,并在 Ubuntu、Debian、Red Hat 5 和 6,甚至是旧的 Solaris 8 机器上的各种 Bash 版本上对其进行了测试。该脚本运行良好,因此已发布。

然而,当用户在 SUSE 机器上尝试它时,它给出了“不是有效的标识符”错误。幸运的是,我对函数名中存在无效字符的猜测是正确的。连字符搞砸了。

至少经过一定程度测试的脚本在另一个 Bash 或发行版上会有完全不同的行为,这一事实令人不安。我怎样才能避免这种情况?

【问题讨论】:

  • 这些机器上的 bash 版本是什么? (特别是 SUSE 机器?)
  • 不幸的是,我现在无法轻松访问它们。他们是 3.something.

标签: linux bash unix syntax


【解决方案1】:

注意这里最大的更正是函数名称中从不允许换行。

我的回答:

  • 重击 --posix: [a-zA-Z_][0-9a-zA-Z_]*
  • 重击 3.0-4.4:[^#%0-9\0\1\9\10 "$&'();<>\`|\x7f][^\0\1\9\10 "$&'();<>\`|\x7f]*
  • 重击 5.0:[^#%0-9\0\9\10 "$&'();<>\`|][^\0\9\10 "$&'();<>\`|]*
    • \1\x7f 现在可以使用了
  • 重击 5.1:[^#%\0\9\10 "$&'();<>\`|][^\0\9\10 "$&'();<>\`|]*
    • 数字可以排在第一位?!是的!
  • 任何 bash 3-5:[^#%0-9\0\1\9\10 "$&'();<>\`|\x7f][^\0\1\9\10 "$&'();<>\`|\x7f]*
    • 与 3.0-4.4 相同
  • 我的建议(意见):[^#%0-9\0-\f "$&'();<>\`|\x7f-\xff][^\0-\f "$&'();<>\`|\x7f-\xff]
    • 正版:[!*+,-./:=?@A-Z\[\]^_a-z{}~][#%0-9!*+,-./:=?@A-Z\[\]^_a-z{}~]*

我的测试版本:

for ((x=1; x<256; x++)); do
  hex="$(printf "%02x" $x)"
  name="$(printf \\x${hex})"
  if [ "${x}" = "10" ]; then
    name=$'\n'
  fi
  if [ "$(echo -n "${name}" | xxd | awk '{print $2}')" != "${hex}" ]; then
    echo "$x failed first sanity check"
  fi
  (
    eval "function ${name}(){ echo ${x};}" &>/dev/null
    if test "$("${name}" 2>/dev/null)" != "${x}"; then
      eval "function ok${name}doe(){ echo ${x};}" &>/dev/null
      if test "$(type -t okdoe 2>/dev/null)" = "function"; then
        echo "${x} failed second sanity test"
      fi
      if test "$("ok${name}doe" 2>/dev/null)" != "${x}"; then
        echo "${x}(${name}) never works"
      else
        echo "${x}(${name}) cannot be first"
      fi
    else
      # Just assume everything over 128 is hard, unless this says otherwise
      if test "${x}" -gt 127; then
        if declare -pF | grep -q "declare -f \x${hex}"; then
          echo "${x} works, but is actually not difficult"
          declare -pF | grep "declare -f \x${hex}" | xxd
        fi
      elif ! declare -pF | grep -q "declare -f \x${hex}"; then
        echo "${x} works, but is difficult in bash"
      fi
    fi
  )
done

一些补充说明:

  • 字符 1-31 不太理想,因为它们更难输入。
  • 字符 128-255 在 bash 中甚至更不理想(macOS 上的 bash 3.2 除外。它可能会以不同的方式编译?)因为像 declare -pF 这样的命令不会呈现特殊字符,即使它们存在于内存中。这意味着任何自省代码都会错误地假设这些函数不存在。但是,compgen 等功能仍然可以正确呈现字符。
  • 超出了我的测试范围,但一些 unicode 也可以工作,尽管在 macOS 上通过 ssh 粘贴/键入更加困难。

【讨论】:

    【解决方案2】:

    来自manual

       Shell Function Definitions
           ...
           name () compound-command [redirection]
           function name [()] compound-command [redirection]
    

    name 在别处定义:

           name   A  word  consisting  only  of alphanumeric characters and under‐
                  scores, and beginning with an alphabetic character or an  under‐
                  score.  Also referred to as an identifier.
    

    所以连字符无效。然而,在我的系统上,它们确实有效......

    $ bash --version
    GNU bash, version 4.2.25(1)-release (x86_64-pc-linux-gnu)
    

    【讨论】:

    • --posix 模式下不允许使用连字符(参见 §6.11),因此支持连字符是一种 Bash 主义。我从手册中不确定 Bash 隐式支持的其他字符,但我知道 :. 也可以用于函数名称(尽管 : 至少会破坏制表符完成)。跨度>
    • 如果使用关键字function 定义问号,则有时允许使用问号。例如,我有一个函数 source? 在采购之前检查是否存在。
    【解决方案3】:

    此脚本测试所有有效字符 带有 1 个字符的函数名称。


    它使用输出 53 个有效字符(a-zA-Z 和下划线)
    POSIX shell220 个有效字符,带有 BASH v4.4.12。

    Ron Burk 的回答是有效的,但缺少数字。

    #!/bin/sh
    
    FILE='/tmp/FOO'
    I=0
    VALID=0
    
    while [ $I -lt 256 ]; do {
            NAME="$( printf \\$( printf '%03o' $I ))"
            I=$(( I + 1 ))
    
            >"$FILE"
            ( eval "$NAME(){ rm $FILE;}; $NAME" 2>/dev/null )
    
            if [ -f "$FILE" ]; then
                    rm "$FILE"
            else
                    VALID=$(( VALID + 1 ))
                    echo "$VALID/256 - OK: $NAME"   
            fi
    } done
    

    【讨论】:

      【解决方案4】:

      这个问题是关于“规则”的,它有两种不同的回答方式,每种方式在某种意义上都是正确的,具体取决于您要称之为“规则”的内容。只是为了充实@rici 的观点,即您可以在函数名称中插入任何字符,我编写了一个小的 bash 脚本来尝试 check 每个可能的 (0-255) 字符作为函数名称,如以及函数名的第二个字符:

      #!/bin/bash
      ASCII=( nul soh stx etx eot enq ack bel bs tab nl vt np cr so si dle \
                  dc1 dc2 dc3 dc4 nak syn etb can em sub esc fs gs rs us sp )
      
      for((i=33; i < 127; ++i)); do
          printf -v Hex "%x" $i
      
          printf -v Chr "\x$Hex"
          ASCII[$i]="$Chr"
      done
      ASCII[127]=del
      for((i=128; i < 256; ++i)); do
          ASCII[$i]=$(printf "0X%x" $i)
      done
      
      # ASCII table is now defined
      
      function Test(){
          Illegal=""
          for((i=1; i <= 255; ++i)); do
              Name="$(printf \\$(printf '%03o' $i))"
              eval "function $1$Name(){ return 0; }; $1$Name ;" 2>/dev/null
              if [[ $? -ne 0 ]]; then
                  Illegal+=" ${ASCII[$i]}"
                  #        echo Illegal: "${ASCII[$i]}"
              fi
          done
          printf "Illegal: %s\n" "$Illegal"
      }
      echo "$BASH_VERSION"
      Test
      Test "x"
      
      # can we really do funky crap like this?
      function [}{(){
         echo "Let me take you to, funkytown!"
      }
      [}{    # why yes, we can!
      # though editor auto-indent modes may punish us
      

      我实际上跳过了 NUL (0x00),因为这是 bash 可能反对在输入流中查找的一个字符。该脚本的输出是:

      4.4.0(1)-release
      Illegal:  soh tab nl sp ! " # $ % & ' ( ) * 0 1 2 3 4 5 6 7 8 9 ; < > \ ` { | } ~ del
      Illegal:  soh " $ & ' ( ) ; < > [ \ ` | del
      Let me take you to, funkytown!
      

      请注意,bash 很高兴地让我将函数命名为“[}{”。可能我的代码不够严格,无法为实践中的合法性提供确切的规则,但它应该能够说明可能的滥用方式。 我希望我可以将此答案标记为“仅限成熟观众”。

      【讨论】:

      • 包含您运行脚本的 Bash 版本会很有帮助。
      • 您不能用全数字名称定义函数,但您可以以数字开头。此外,这里的一些假定非法行为是因为在调用函数时引用错误。例如,您可以将函数命名为*...,但该测试的执行方式会产生错误。因此,您不能在函数名称的第一位拥有的实际字符列表小于此处的列表......但是第二位字符的列表更长,因为再次,此脚本没有调用该函数正确。
      • @PJEby 你可以吗?在 4.4.18(1) 发布时,*() { echo foo; } 给了我bash: syntax error near unexpected token `}'\*() { echo foo; } 给了我bash: `\*': not a valid identifier。 ('*'() ... 也一样)
      • 可以使用function * { echo hello; }(在 Bash 4.4 中),但在调用它时可能需要转义它以避免通配符。 ~(波浪号扩展)和!(否定退出代码)相同。如果您有单字符文件名,? 将与 * 相同。你也可以定义一个函数%,但我找不到调用它的方法,因为仅仅给出一个以% 开头的单词与在其上运行fg 相同,即使被转义。如果禁用interactive_comments,则只能在交互式 shell 中使用# 作为函数名(非交互式shell 总是将# 作为注释标记)。
      • @muru - 你需要在 *() 之间留一个空格才能工作。不需要`\`。
      【解决方案5】:

      命令标识符和变量名有不同的语法。变量名称仅限于字母数字字符和下划线,不能以数字开头。另一方面,命令名称几乎可以是任何不包含 bash 元字符的名称(即使这样,它们也可以被引用)。

      在 bash 中,函数名可以是命令名,只要它们被解析为不带引号的 WORD 即可。 (除了,由于某种原因,它们不能是整数。)但是,这是一个 bash 扩展。如果目标机器正在使用其他一些 shell(例如 dash),它可能不起作用,因为 Posix 标准 shell 语法只允许函数定义形式中的“NAME”(并且还禁止使用保留字)。

      【讨论】:

      • 但是,SUSE 机器使用的是 Bash(根据 echo $SHELL)。当股票 Bash 其他主要发行版不关心时,我不确定为什么它对连字符不满意。
      • @labyrinth: "echo $SHELL" 不会告诉你哪个 shell 正在执行。它告诉您当前用户的默认 shell 是什么。因此,无论出于何种原因,该命令很可能在不同的 shell 中运行(例如,它在以 #!/bin/sh 的 shebang 行开头的脚本中)。另一种可能性,虽然看起来不太可能,是脚本在“posix”shell中运行,或者因为设置了环境变量POSIXLY_CORRECT,或者因为set -p被执行,或者-p被包含在命令中bash 的 -line 选项。
      • 不,脚本中没有shebang行,所以如果它没有使用默认的登录shell,那肯定是不同的原因。
      • @labyrinth:如果脚本没有 shebang 行,则没有明确定义将调用哪个 shell 来执行它。它可能是 /bin/sh(这是 execlp 库调用的行为),或者它可能是当前 shell(bashksh 以这种方式处理缺少的 shebang 行。)如果脚本被显式调用sh script,那么当然是sh执行它,不管shebang行或$SHELL设置。
      【解决方案6】:

      来自3.3 Shell Functions

      Shell 函数是一种对命令进行分组以供以后执行的方法,该命令使用组的单个名称。它们就像“常规”命令一样执行。当 shell 函数的名称用作简单命令名称时,将执行与该函数名称关联的命令列表。 Shell 函数在当前 shell 上下文中执行;没有创建新的进程来解释它们。

      函数使用以下语法声明:

      name () compound-command [ redirections ]
      

      function name [()] compound-command [ redirections ]
      

      来自2 Definitions

      名字

      仅由字母、数字和下划线组成并以字母或下划线开头的单词。名称用作 shell 变量和函数名称。也称为标识符。

      【讨论】:

      • 函数名和变量名的规则略有不同。例如,函数名称中可以包含 .-(以及其他特殊字符),但不能包含 shell 变量。
      • 请参阅 this answer 以获得更准确的描述。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2011-06-15
      • 2017-08-14
      • 2020-06-20
      • 2015-01-22
      • 2020-11-06
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多