从 bash 中的子字符串中提取字符串(是的,就是这样)

Extracting a string from a substring in bash (yes, that way around)

我在bash中有一个由几个单词组成的字符串,叫做comp_line,里面可以有任意数量的空格。例如:

"foo bar   apple  banana q xy"

我有一个从零开始的索引 comp_point 指向该字符串中的一个字符,例如如果 comp_point 为 4,则指向 'bar'.

中的第一个 'b'

仅基于comp_pointcomp_line,我想提取索引指向的单词,其中“单词”是字母、数字、标点符号或任何的序列其他非空白字符,两边都被空白包围(如果这个词在字符串的开头或结尾,或者是字符串中唯一的词,它应该以同样的方式工作。)

我要提取的单词将变为 cur(当前单词)

基于此,我想出了一套规则:

读取当前字符curchar、前一个字符prevchar和下一个字符nextchar。那么:

  1. 如果curchar是图形字符(非空白),将cur设置为curchar前后的字母(直到出现空白为止或两边的字符串 start/end。)

  2. 否则,如果 prevchar 是图形字符,将 cur 设置为从前一个字母开始的字母,向后直到前一个空格 character/string 开始。

  3. 否则,如果nextchar是图形字符,将cur设置为从下一个字母开始的字母,向前直到下一个空格character/string结束。

  4. 如果满足以上条件中的none(意思是curcharnextcharprevchar都是空白字符,)设置cur""(空字符串)

我已经编写了一些我认为可以实现此目的的代码。规则 2、3 和 4 相对简单,但规则 1 是最难实现的 - 我不得不做一些复杂的字符串切片。我不相信我的解决方案在任何方面都是理想的,并且想知道是否有人知道仅在 bash 内执行此操作的更好方法(不外包给 Python 或其他更简单的语言。)

测试于 https://rextester.com/l/bash_online_compiler

#!/bin/bash
# GNU bash, version 4.4.20

comp_line="foo bar   apple  banana q xy"
comp_point=19
cur=""

curchar=${comp_line:$comp_point:1}
prevchar=${comp_line:$((comp_point - 1)):1}
nextchar=${comp_line:$((comp_point + 1)):1}
echo "<$prevchar> <$curchar> <$nextchar>"

if [[ $curchar =~ [[:graph:]] ]]; then
    # Rule 1 - Extract current word
    slice="${comp_line:$comp_point}"
    endslice="${slice%% *}"
    slice="${slice#"$endslice"}"
    slice="${comp_line%"$slice"}"
    cur="${slice##* }"
else
    if [[ $prevchar =~ [[:graph:]] ]]; then
        # Rule 2 - Extract previous word
        slice="${comp_line::$comp_point}"
        cur="${slice##* }"
    else
        if [[ $nextchar =~ [[:graph:]] ]]; then
            # Rule 3 - Extract next word
            slice="${comp_line:$comp_point+1}"
            cur="${slice%% *}"
        else
            # Rule 4 - Set cur to empty string ""
            cur=""
        fi
    fi
fi

echo "Cur: <$cur>"

当前示例将 return 'banana' 因为 comp_point 设置为 19.

我确信一定有一种我没有想到的更简洁的方法,或者我错过的一些技巧。到目前为止它也有效,但我认为可能有一些我没有想到的边缘情况。任何人都可以建议是否有更好的方法吗?


(XY问题,如果有人问的话)

我正在编写制表符完成脚本,并尝试使用 COMP_LINE 和 COMP_POINT 来模拟 COMP_WORDS 和 COMP_CWORD 的功能。当用户键入命令以完成制表符时,我想根据后两个变量计算出他们试图完成制表符的单词。我不想将此代码外包给 Python,因为当 Python 涉及制表符完成时,性能会受到很大影响。

if anyone knows of a better way to do this within bash only

使用正则表达式。使用 ^.{4} 可以跳过前四个字母以导航到索引 4。使用 [[:graph:]]* 可以匹配该索引处单词的其余部分。 * 比较贪心,会匹配尽可能多的图形字符。

wordAtIndex() {
  local index= string= left right indexFromRight
  [[ "$string" =~ ^.{$index}([[:graph:]]*) ]]
  right=${BASH_REMATCH[1]}
  ((indexFromRight=${#string}-index-1))
  [[ "$string" =~ ([[:graph:]]*).{$indexFromRight}$ ]]
  left=${BASH_REMATCH[1]}
  echo "$left${right:1}"
}

这里是对您的示例的完整测试:

string="foo bar   apple  banana q xy"
for ((i=0; i < "${#string}"; ++i)); do
  printf '%s <-- "%s"\n' "${string:i:1}" "$(wordAtIndex "$i" "$string")"
done

这会在左侧垂直输出输入字符串,并在每个索引上提取索引指向右侧的单词。

f <-- "foo"
o <-- "foo"
o <-- "foo"
  <-- ""
b <-- "bar"
a <-- "bar"
r <-- "bar"
  <-- ""
  <-- ""
  <-- ""
a <-- "apple"
p <-- "apple"
p <-- "apple"
l <-- "apple"
e <-- "apple"
  <-- ""
  <-- ""
b <-- "banana"
a <-- "banana"
n <-- "banana"
a <-- "banana"
n <-- "banana"
a <-- "banana"
  <-- ""
q <-- "q"
  <-- ""
x <-- "xy"
y <-- "xy"

bash 中的另一种方式,没有数组。

#!/bin/bash

string="foo bar   apple  banana q xy"

wordAtIndex() {
  local index= string= ret='' last first
  if [ "${string:index:1}" != " " ] ; then
    last="${string:index}"
    first="${string:0:index}"
    ret="${first##* }${last%% *}"
  fi
  echo "$ret"
}

for ((i=0; i < "${#string}"; ++i)); do
 printf '%s <-- "%s"\n' "${string:i:1}" "$(wordAtIndex "$i" "$string")"
done