从 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_point
和comp_line
,我想提取索引指向的单词,其中“单词”是字母、数字、标点符号或任何的序列其他非空白字符,两边都被空白包围(如果这个词在字符串的开头或结尾,或者是字符串中唯一的词,它应该以同样的方式工作。)
我要提取的单词将变为 cur
(当前单词)
基于此,我想出了一套规则:
读取当前字符curchar
、前一个字符prevchar
和下一个字符nextchar
。那么:
如果curchar
是图形字符(非空白),将cur
设置为curchar
前后的字母(直到出现空白为止或两边的字符串 start/end。)
否则,如果 prevchar
是图形字符,将 cur
设置为从前一个字母开始的字母,向后直到前一个空格 character/string 开始。
否则,如果nextchar
是图形字符,将cur
设置为从下一个字母开始的字母,向前直到下一个空格character/string结束。
如果满足以上条件中的none(意思是curchar
,nextchar
和prevchar
都是空白字符,)设置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
我在bash中有一个由几个单词组成的字符串,叫做comp_line
,里面可以有任意数量的空格。例如:
"foo bar apple banana q xy"
我有一个从零开始的索引 comp_point
指向该字符串中的一个字符,例如如果 comp_point
为 4,则指向 'bar'.
仅基于comp_point
和comp_line
,我想提取索引指向的单词,其中“单词”是字母、数字、标点符号或任何的序列其他非空白字符,两边都被空白包围(如果这个词在字符串的开头或结尾,或者是字符串中唯一的词,它应该以同样的方式工作。)
我要提取的单词将变为 cur
(当前单词)
基于此,我想出了一套规则:
读取当前字符curchar
、前一个字符prevchar
和下一个字符nextchar
。那么:
如果
curchar
是图形字符(非空白),将cur
设置为curchar
前后的字母(直到出现空白为止或两边的字符串 start/end。)否则,如果
prevchar
是图形字符,将cur
设置为从前一个字母开始的字母,向后直到前一个空格 character/string 开始。否则,如果
nextchar
是图形字符,将cur
设置为从下一个字母开始的字母,向前直到下一个空格character/string结束。如果满足以上条件中的none(意思是
curchar
,nextchar
和prevchar
都是空白字符,)设置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