LSB
如何将没有分隔符的连续字符串拆分为 3 个字符的组Msb
并存储在数组中?
例如,bcdefghijhk
应该变成:
bc def ghi jhk
b cdef fghi ijhk
使用纯 bash 字符串操作。反转字符串不是一个选项,因为需要保留顺序,例如。三连音 jhk 不能是 khj
答案1
这是我发现的一种按照您要求的方式分割字符串的方法。我将其编写为 Bash 子例程(又名函数),因为我有一种感觉,您可以通过这种方式将其合并到您自己的脚本中:
#!/usr/bin/env bash
number=bcdefghijhk
chunk=2
number_parts=() # global variable
function slice_number {
local num="$1"
local chk="$2"
local len=${#num}
local pos=0
local chk_first=$(( len % chk ))
# the first chunk is smaller when the number length
# is not an exact multiple of chunk-size
if [[ $chk_first -gt 0 ]]; then
number_parts[${#number_parts[@]}]="${num:$pos:$chk_first}"
pos=$(( pos + chk_first ))
fi
# collect the rest of the number as full-size chunks
while (( (pos + chk) <= len )); do
number_parts[${#number_parts[@]}]="${num:$pos:$chk}"
pos=$(( pos + chk ))
done
}
# call the slicing routine
slice_number "${number}" ${chunk}
# now $number_parts[] has the sliced up number
printf '%s ' "${number_parts[@]}"
echo
我将字符串称为“数字”,因为您将其描述为具有 MSB 和 LSB,但这将其作为字符串进行操作。我将您请求的组的大小(3 或 4)称为“块”大小。
起初,该任务似乎非常困难,因为字符串的最右端必须是全尺寸块,而最左端必须是部分块。大多数字符串解析都想做相反的事情。然后我意识到总字符串长度以块大小为模将给出第一个(最左边)块的减少长度,而其余块将恰好是块大小。第一个块可以是特殊情况,其余部分是一个简单的循环,通过字符串推进下一个切片的起始位置。
该例程将块保存在数组中,数组中的 0(第一个)元素保存字符串的最左边的块,数组中的最后一个元素保存最右边的块。我的脚本的最后一行显示了一个使用空格分隔符打印块的示例(尽管末尾有一个尾随空格),这就是您指定的输出类型。可以修改代码以组装字符串而不是数组。
该数组填充了我经常使用但在其他地方不常见的技术。在从零开始的数组中,数组元素的数量恰好是数组末尾之后的下一个元素的索引。即,将新元素“推入”到数组末尾将填充该索引。它使数组分配看起来很复杂,但它确实有效。
最后,此示例子例程不会对给定的字符串或块大小参数执行任何检查。如果 chunk-size 为 0 或负数,或者有非数字字符,则应该有一些处理;如果块大小大于或等于字符串的长度,子例程应采取特殊操作。如果字符串为空,还要处理。
答案2
如果可以选择从 切换bash
到:zsh
$ set -o extendedglob
$ string=bcdefghijhk
$ print -r -- ${(j[])${(Oas[])${${(j[])${(Oas[])string}}//(#m)???/$MATCH }}}
bc def ghi jhk
$ print -r -- ${(j[])${(Oas[])${${(j[])${(Oas[])string}}//(#m)????/$MATCH }}}
bcd efgh ijhk
在哪里:
s[]
分裂于没有什么意味着字符串被分成它的字符组成部分。Oa
反向a
阵列O
顺序。j[]
不连接任何数组元素,因此这 3 个运算符组合起来会反转一个字符串。${var//pattern/replacement}
常用的 ksh93 替换运算符(#m)
(needsextendedglob
) 导致匹配项存储在 中$MATCH
。
请注意,如果字符串长度是半字节的精确倍数,您将得到一个前导空格(您可以使用 来修剪${result# }
)。
也可以看看此处另一个相关问答中的其他方法。
答案3
这可以理解为(主要)数学问题。
可以使用通用语法提取一定长度的字符(bash 中的单字节)${str:start:length}
。开始和长度都是数字。定义起始位置和要寻址的字符数的数字。
给定一个字符串str
和一个width
数字,第一个字符块的长度应为echo $(( ${#str} % w ))
。这是字符串中无法分成 3 个字符的块的部分。
对于提供的 str 和请求的宽度之一 (3),我们得到:
$ str='bcdefghijhk'
$ w=3
$ echo "$(( ${#str}%w ))"
2
我们称这个值为o
(偏移量),然后我们可以这样做:
$ str='bcdefghijhk'; w=3; o=$(( ${#str}%w ))
$ for (( i=o-w ; i<=${#str} ; i+=w )); do
start="$(( i>0?i:0 ))"; # if the index is negative, use 0
part="$(( i<0 ? o : w ))"; # use offset or w
echo "${str:start:part}"; # extract one part.
done
bc
def
ghi
jhk
是的,第一个索引可能是负数,这可以理解为相当于模运算中的偏移值。
当然,这不会以请求的格式打印字符串,如果整个过程可以在函数中定义,那就更好了。通过这些更改和大量减少,我们可以编写:
$ cat tst.sh
#!/bin/bash --
split()
{
local str=$2 w=$1 o i arr IFS=" ";
local o=$((${#str}%w));
arr=();
for (( i=(o==0?0:o-w) ; i<${#str} ; i+=w )); do
if (( i<0 )); then
arr+=("${str:0:o}");
continue;
fi
arr+=("${str:i:w}")
done;
printf "%s\n" "${arr[*]}";
}
split "${@}"
$ chmod u+x tst.sh
$ ./tst.sh 3 'bcdefghijhk'
bc def ghi jhk
$ ./tst.sh 4 'bcdefghijhk'
bcd efgh ijhk