【问题标题】:How to do string separation using using keyword?如何使用关键字进行字符串分隔?
【发布时间】:2014-09-17 09:52:51
【问题描述】:

我有下面给出的字符串。每个键之间没有特定的分隔符。唯一的方法是识别键是使用关键字“key_1”或“key_2”等。

所有键都以“key_”开头,并且永远不能出现在另一个值中:

STRING="key_1=mislanious_string1 key_2=miscellaneous_string2"

我想要如下的输出。

echo $STRING1 应该打印:

key_1=mislanious_string1

echo $STRING2 应该打印:

key_2=mislanious_string2

例如: 如果 STRING="key_1=foobarzkey_2=bash" ,则输出应类似于 STRING1=key_1=foobarz 和 STRING2=key_2=bash。

可能有更多的键,如 key_1 、 key_2 、 key_3 等。每个键都以“key_”开头,并且永远不能出现在另一个键的值中:

如何在 UNIX bash shell 中做到这一点?

【问题讨论】:

  • 如何在不知道分隔符的情况下识别键的开始位置?
  • "key_" 关键字来识别每个键。
  • “每个键之间没有特定的分隔符”是什么意思?有空位吗?
  • STRING="key_1=foobarzkey_2=bash"。这是对key_1 的一项分配还是对key_1key_2 的单独分配?如果z 是分隔符,则为第二个,否则为第一个。
  • 请用您分散在各地的 cmets 的所有修正更新您的问题。您并没有在发布的问题中向人们提供完整的图片,人们不应该通过搜索 cmets 来弄清楚您真正想要做什么。

标签: bash shell unix ksh


【解决方案1】:

有时可能会忽略最简单的解决方案:

STRING="key_1=mislanious_string1key_2=miscellaneous_string2"

read STRING1 STRING2<<<${STRING//key_/ key_}

echo $STRING1
echo $STRING2

【讨论】:

    【解决方案2】:

    这个答案最初不能识别前面没有空格的键。这已得到修复。在目前的形式中,这个答案作为一个便携解决方案提供了价值。如果您不同意,请告诉我。

    Glenn Jackmananubhava 提供的答案很有帮助,但使用 GNU 扩展并非在所有平台上都可用grep -Pawk 和多字符。RS 值)。

    这是一个POSIX 兼容 sed 解决方案,它应该适用于大多数平台,使用bashkshzsh 作为外壳:

    str='key_1=mislanious_string1 key_2=miscellaneous_string2key_3=last'
    
    while read -r varDef; do 
      [[ -n $varDef ]] && typeset "$varDef"
    done < <(sed 's/\(key_\([0-9]\{1,\}\)=\)/\'$'\n''string\2=\1/g' <<<"$str")
    
    #'# Print the variables created ($string1, $string2, $string3).
    typeset -p ${!string@}
    

    请注意,小写变量名(string1,...)用于防止与环境变量的潜在冲突。

    • sed 用于将字符串拆分为键值标记,每个标记在各自的行中,前面是所需的目标变量名称和=,有效地输出 shell 变量分配;例如,对于key_1,sed 命令传递出去:
      string1=key_1=mislanious_string1 
    • while 循环然后读取每个输出行并使用typeset 来声明和分配变量(请注意,选择typeset 是为了与ksh 兼容 - 而typeset 也适用于bash 和@ 987654343@ 你通常会在那里使用declare); [[ -n $varDef ]] 忽略 sed 输出开头的空行。
    • 注意:此解决方案从值中修剪 尾随 空格,与问题中的示例一致。这种修剪是由于使用了 read 和默认的 $IFS 值(内部字段分隔符)而发生的 - 要保留尾随空格,只需使用 IFS= read 而不是仅使用 read
    • 另请注意,需要使用进程替换来提供输入 (while ... &lt;(sed ...))(与管道 (sed ... | while ...) 不同,以确保在当前 shell 中定义变量(而不是在 subshel​​l 中,这将导致变量对当前 shell 不可见)。

    关于使上述sed 命令符合 POSIX 标准的一些背景信息:

    • POSIX 只要求sed基本 正则表达式,这会带走许多功能(例如,量词?+,交替(|))并使转义更加麻烦(例如,() 必须为 \-转义)。
    • POSIX sed 也不支持转义序列,例如传递给s替换 字符串中的\n,因此ANSI-C quoting 用于拼接\-转义actual 使用$'\n' 在替换字符串中换行。

    作为非 POSIX GNU sed 扩展的有用性的示例,这里有一个充分利用 GNU sed 功能的等效命令(扩展正则表达式,支持\n),从而使命令更短、更易读:

    sed -r 's/(key_([0-9]+)=)/\nstring\2=\1/g' <<<"$str"    
    

    【讨论】:

    • 一个复杂的因素是密钥对可能没有之间有任何空格:key_1=onekey_2=two
    • @glennjackman 感谢您的理解 - 现在已修复。该修复程序实际上允许一个简单的 POSIX 解决方案(唯一需要注意的是从值中删除了尾随空格,但我认为没关系)。
    • 刚刚注意到当 gnu grep/awk 不可用 +1 时,对这个有用的答案进行了不必要的反对。
    • @anubhava:谢谢 - 我很感激。
    【解决方案3】:

    使用grep -P (PCRE) 支持输入中的多个键值对:

    STRING="key_1=mislanious_string1key_2=miscellaneous_string2key_3=fookey_4=BASH"
    grep -oP 'key_[^=]+=.*?(?=key_|$)' <<< "$STRING"
    key_1=mislanious_string1
    key_2=miscellaneous_string2
    key_3=foo
    key_4=BASH
    

    要将它们存储到 BASH 数组中,您可以使用:

    read -d '' -ra arr < <(grep -oP 'key_[^=]+=.*?(?=key_|$)' <<< "$STRING")
    printf "%s\n" "${arr[@]}"
    key_1=mislanious_string1
    key_2=miscellaneous_string2
    key_3=foo
    key_4=BASH
    
    declare -p arr
    declare -a arr='([0]="key_1=mislanious_string1" [1]="key_2=miscellaneous_string2" [2]="key_3=foo" [3]="key_4=BASH")'
    

    UPDATE:: 这是一种拆分这些字符串的纯 BASH(非 gnu)方法。我们首先在每次出现key_ 字符串之前插入一个不可见字符,然后使用它来拆分字符串:

    STRING="key_1=mislanious_string1key_2=miscellaneous_string2key_3=fookey_4=BASH"
    c=$'\x06'
    s="${STRING//key_/${c}key_}"
    arr=()
    while [[ "$s" =~ ${c}(key_[^=]+=[^${c}]+)(.*) ]]; do
       arr+=( "${BASH_REMATCH[1]}" )
       s="${BASH_REMATCH[2]}"
    done
    

    然后进行测试:

    printf "<%s>\n" "${arr[@]}"
    <key_1=mislanious_string1>
    <key_2=miscellaneous_string2>
    <key_3=foo>
    <key_4=BASH>
    

    【讨论】:

    • +1 表示带有前瞻条件的正则表达式 - 但是请注意,grep -P 是一个 GNU 扩展。您的数组创建语句错误地创建了一个 single 数组元素,因为整个命令替换都被双引号引起来。您需要改用 read -ra(或 bash 4.x 中的 readarray):read -d '' -ra arr &lt; &lt;(grep -oP 'key_[^=]+=.*?(?=key_|$)' &lt;&lt;&lt; "$STRING")
    • 感谢@mklement0 是的,这适用于 gnu grep(在 OSX 上,我使用 home brew 安装了它)。我还更正了数组创建中的引用。
    • 谢谢。我仍然推荐使用read -ra 方法来创建数组,因为您当前的方法由于两个原因而不稳定:(1)它不能按预期使用带有嵌入空格的行(在这里可能不是问题 - 可修复),但是,或许更重要的是,(2) 输入行受路径名扩展的影响。
    • 谢谢,这是非常有效的观点。我又改正了。非常感谢。
    • @mklement0:受您的回答启发,我现在为这个问题提供了一个纯 BASH(非 GNU)解决方案。谢谢。
    【解决方案4】:

    我最喜欢 anubhava 的 grep -oP 解决方案。这是一个 awk 解决方案:

    STRING="key_15=foobarzkey_3=bash"
    awk -v RS="key_" 'NR>1{split($0, a, /=/); print "STRING" a[1] "=" RS $0}' <<< "$STRING"
    
    STRING15=key_15=foobarz
    STRING3=key_3=bash
    

    因此,将输出创建为 shell 变量

    eval $(awk -v RS="key_" 'NR>1{split($0, a, /=/); print "STRING" a[1] "=" RS $0}' <<< "$STRING")
    echo $STRING3    # => key_3=bash
    echo $STRING15   # => key_15=foobarz
    

    【讨论】:

    • +1 表示优雅的 awk 命令(需要 GNU awkmawk)。 eval 很方便,但很容易出错(例如,只需将 ;ls 附加到输入字符串)。我的回答中的while read -r def ... declare "$def" ...(或typeset)方法更安全。
    猜你喜欢
    • 2012-12-25
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-02-14
    • 2011-07-27
    • 2016-07-05
    • 2011-07-03
    • 2020-04-25
    相关资源
    最近更新 更多