【问题标题】:bash parameter substitution with newline用换行符替换 bash 参数
【发布时间】:2012-04-16 12:32:04
【问题描述】:

我的 shell 脚本似乎有点生疏了。我的愿望是在 bash 中循环一个 arraylist 配置变量,并使用在这个循环中获得的必要参数调用一个函数,所有这些都不需要分叉。

基本上,我已经创建了一个内部脚本人类可解析的逗号分隔配置变量,如下所示:

CONFIG="
     0, 0x00, 'Reset the chip, enable PLL with P=4, R=1, J=9, D=6338' %
     1, 0x01, 'Reset the chip, enable PLL with P=4, R=1, J=9, D=6338' %
    51, 0x10, 'Reset the chip, enable PLL with P=4, R=1, J=9, D=6338' %
    63, 0xd4, 'Power up and unmute DAC' %
    64, 0x00, 'Power up and unmute DAC' %
"

然后我想像这样循环它的参数:

while read reg val expl; do
    printf "%s %s\n" "Calling i2c_write() with reg=${reg//,/}" \
          "val=${val//,/} expl=$expl __EOL__";
    # i2c_write() call
done <<< "${CONFIG//\%/$'\n'}"

当前输出为:

Calling i2c_write() with reg= val= expl= __EOL__
Calling i2c_write() with reg=0 val=0x00 expl='Reset the chip, enable PLL with P=4, R=1, J=9, D=6338' __EOL__
Calling i2c_write() with reg= val= expl= __EOL__
Calling i2c_write() with reg=1 val=0x01 expl='Reset the chip, enable PLL with P=4, R=1, J=9, D=6338' __EOL__
Calling i2c_write() with reg= val= expl= __EOL__
Calling i2c_write() with reg=51 val=0x10 expl='Reset the chip, enable PLL with P=4, R=1, J=9, D=6338' __EOL__
Calling i2c_write() with reg= val= expl= __EOL__
Calling i2c_write() with reg=63 val=0xd4 expl='Power up and unmute DAC' __EOL__
Calling i2c_write() with reg= val= expl= __EOL__
Calling i2c_write() with reg=64 val=0x00 expl='Power up and unmute DAC' __EOL__
Calling i2c_write() with reg= val= expl= __EOL__
Calling i2c_write() with reg= val= expl= __EOL__

期望的输出是:

Calling i2c_write() with reg=0 val=0x00 expl='Reset the chip, enable PLL with P=4, R=1, J=9, D=6338' __EOL__
Calling i2c_write() with reg=1 val=0x01 expl='Reset the chip, enable PLL with P=4, R=1, J=9, D=6338' __EOL__
Calling i2c_write() with reg=51 val=0x10 expl='Reset the chip, enable PLL with P=4, R=1, J=9, D=6338' __EOL__
Calling i2c_write() with reg=63 val=0xd4 expl='Power up and unmute DAC' __EOL__
Calling i2c_write() with reg=64 val=0x00 expl='Power up and unmute DAC' __EOL__

我很乐意将 CONFIG 变量替换为更合适的结构,只要:

  • a) 不需要分叉来循环遍历变量的条目,并且
  • b) 人工解析和编辑所述变量的条目相当容易,并且
  • c) 它适用于 bash 3.2.x 以上版本。

【问题讨论】:

  • 阅读了所有可行的解决方案后,我总觉得我的尝试相当复杂;我只是找不到合适的解决方案。我要感谢 Peter.O、Sorpigal 和 ormaaj,感谢他们鼓舞人心的 cmets 和精心制作的不言自明的代码 sn-ps。每天都能捡到新东西真是太好了。

标签: bash arraylist newline substitution


【解决方案1】:

有必要保留这种复杂的 CONFIG 格式吗?

这应该可行:

#!/bin/bash
CONFIG="
     0, 0x00, 'Reset the chip, enable PLL with P=4, R=1, J=9, D=6338'
     1, 0x01, 'Reset the chip, enable PLL with P=4, R=1, J=9, D=6338'
    51, 0x10, 'Reset the chip, enable PLL with P=4, R=1, J=9, D=6338'
    63, 0xd4, 'Power up and unmute DAC'
    64, 0x00, 'Power up and unmute DAC'
"

while IFS=,$'\n ' read -r reg val expl; do
    [ -z "$reg" ] && continue
    printf "%s %s\n" "Calling i2c_write() with reg=${reg}" \
          "val=${val} expl=$expl __EOL__";
    # i2c_write() call
done <<< "${CONFIG}"

我只是用文字换行符替换了%,因为无论如何你后来都这样做了,并且, 也是一个分隔符。我还进行了一个测试以跳过前导和尾随的空行。

如果必须,您可以将% 保留为换行符,其余部分保持不变。

编辑:

如果您对保持复杂的 CONFIG 格式的约束更少,我可以提出以下替代方案吗?

#!/bin/bash

config+=(0 0x00 'Reset the chip, enable PLL with P=4, R=1, J=9, D=6338')
config+=(1 0x01 'Reset the chip, enable PLL with P=4, R=1, J=9, D=6338')
config+=(51 0x10 'Reset the chip, enable PLL with P=4, R=1, J=9, D=6338')
config+=(63 0xd4 'Power up and unmute DAC')
config+=(64 0x00 'Power up and unmute DAC')

for((i=0;i<${#config[@]};i+=3)) ; do
    reg=${config[$i]}
    val=${config[$i+1]}
    expl="${config[$i+2]}"
    printf "Calling i2c_write() with reg=%d val=%s expl='%s' __EOL__\n" \
            $reg $val "$expl"
    # i2c_write() call
done

这假定您总是有 3 个参数集,这看起来是正确的,并且避免了混乱的解析逻辑。它应该稍微更有效率。我还将printf 更改为更简单。

编辑 2: 与 Peter.O 竞争,这是一个在 sh 中运行的版本:

#!/bin/sh
CONFIG="
     0, 0x00, 'Reset the chip, enable PLL with P=4, R=1, J=9, D=6338'
     1, 0x01, 'Reset the chip, enable PLL with P=4, R=1, J=9, D=6338'
    51, 0x10, 'Reset the chip, enable PLL with P=4, R=1, J=9, D=6338'
    63, 0xd4, 'Power up and unmute DAC'
    64, 0x00, 'Power up and unmute DAC'
"

echo "$CONFIG" | while IFS=' ', read -r reg val expl; do
    [ -z "$reg" ] && continue
    printf "Calling i2c_write() with reg=%d val=%s expl=%s __EOL__\n" \
            $reg $val "$expl"
    # i2c_write() call
done

【讨论】:

  • 有趣的解决方案:我当然不知道 "IFS=,$'\n '" 部分。其次,复杂的 CONFIG 充其量是代码易读性的障碍,因为它最终会超出可接受的范围,并且必须一起放入单独的配置文件中。我非常喜欢你的第二个版本,如果我没记错的话,你会在第三个选项中输入一个子 shell。
  • @Moreaki:第三个版本中的子shell只是由于管道。您可以像 Peter.O 那样使用此处的刺痛并避免这种情况;事实上,现在我想到了一个在其中扩展$CONFIG 的字符串将完成我想要的(将 CONFIG 的声明与其内容的处理隔离开来)而不会导致这个问题。
【解决方案2】:

这将在sh 中运行,所以我认为它将在bash 3.1 中运行。它不喜欢 regvar 参数替换 (//,/) 所以我只是将它们更改为截断逗号。并将输入法更改为here-doc vs 你的here-string

我不确定为什么每个输入行的末尾都有%。只是在那里放一个换行符吗? (但那里已经有一个换行符,% 替换只是添加了空行)......

这是修改后的脚本

while read -r reg val expl; do
    printf "%s %s %s\n" \
      "Calling i2c_write() with reg=${reg%,}" \
                               "val=${val%,}" \
                               "expl=$expl __EOL__";
done <<EOF
     0, 0x00, 'Reset the chip, enable PLL with P=4, R=1, J=9, D=6338'
     1, 0x01, 'Reset the chip, enable PLL with P=4, R=1, J=9, D=6338'
    51, 0x10, 'Reset the chip, enable PLL with P=4, R=1, J=9, D=6338'
    63, 0xd4, 'Power up and unmute DAC'
    64, 0x00, 'Power up and unmute DAC'
EOF

这是一个awk 版本(只是为了比较)。它读取相同的数据。

awk -vFS=\' '{split($1,f," ")
              split(f[1]f[2],f,",")
              print "Calling i2c_write() with" \
                     " reg=" f[1] \
                     " val=" f[2] \
                     " expl="FS $2 FS" __EOL__"
}' <<EOF
:: data ::
END

sed 很好的衡量标准......

sed -nr "s/^ +([0-9]+), +([0-9a-fx]+), +('.*')$/\
Calling i2c_write() with reg=\1 val=\2 expl=\3 __EOL__/p
" <<EOF
:: data ::
END

【讨论】:

  • 我完全忘记了这里文档的方法;)。也感谢您提供 awk 和 sed 版本。最初出于绝望而选择的 '%' 分隔符就像一头从荨麻上舔尿的猪一样丑陋。我将注释部分从单引号稍微修改为双引号,遵循更合理的 CSV 规范。
  • @Moreaki:小心使用双引号。单引号更安全,因为它们确保 shell 根本不会处理内容,而双引号则没有这样的保证。
【解决方案3】:
main() {
    local a n
    while read -r a; do
        local -a 'args=('"$a"')'
        printf "%s reg=%s val=%s expl='%s' __EOL__\n" 'Calling i2c_write() with' "${args[@]}"
        # i2c_write {reg,val,expl}"=${args[n++%3]}"
    done <<"EOF"
0 0x00 'Reset the chip, enable PLL with P=4, R=1, J=9, D=6338'
1 0x01 'Reset the chip, enable PLL with P=4, R=1, J=9, D=6338'
51 0x10 'Reset the chip, enable PLL with P=4, R=1, J=9, D=6338'
63 0xd4 'Power up and unmute DAC'
64 0x00 'Power up and unmute DAC'
EOF
}

main

我假设您正在尝试在此处存储 3 个字段的“结构”并将它们作为 3 个单独的参数传递。注释行是我希望您可能会调用该命令的方式。由于您的 printf 仅通过了一个参数,因此问题不是很清楚应该传递的参数是什么。如果是这样的话,那么这个问题就简单多了。

假设您使用的是带有数组的外壳,那么您绝对需要一个数组。不幸的是,Bash 缺少多维数组,而 bash

上述未记录的、不可移植的、不保证未来安全的黑客攻击在当前版本之前应该在 Bash 3 上“安全”并满足您的要求。 bash 3 的限制非常痛苦,因为我们不能使用printf -v,而且没有分叉意味着没有命令替换,也就意味着没有printf '%q'。基本上,heredoc 的每一行都必须是通常是复合赋值的有效内容(正确引用和转义)。

http://mywiki.wooledge.org/BashFAQ/050

【讨论】:

  • 您的解决方案为我提供了一些有趣的见解,帮助我解决未来在 bash shell 上下文中读取和提供数组的问题。非常感谢你。在一个侧面节点上,我受到您关于缺乏关联数组的评论的启发,并提出了一个半支持的解决方案来解决这个问题:
  • 我正在开发一个库,用于泛化传递/返回数组和简单的高阶函数,但它还没有完全在 Github 上。这种特殊行为是 obscure。如果需要,我在这里有一个更复杂的例子:wiki.bash-hackers.org/syntax/arrays#indirection。如果您需要更好的数据结构,不能使用非 shell 语言,并且性能很重要,我建议您查看当前的 ksh93 版本。
  • 在等待 bitback/openembedded 编译世界时,我很快就使用散列破解了一些东西:pastebin.com/THFkY7BE 它真的很慢并且有一个不幸的参数扩展错误,但它可以满足人们的需求。我有时使用 zsh 或 ksh93,但通常要解决的问题必须比现有版本快几个数量级。这几乎总是导致在 Perl 或 C 中重写。
  • +1 非常有趣地使用 declare(和链接!)
猜你喜欢
  • 1970-01-01
  • 2011-10-19
  • 1970-01-01
  • 1970-01-01
  • 2011-11-05
  • 1970-01-01
  • 1970-01-01
  • 2013-06-29
  • 1970-01-01
相关资源
最近更新 更多