Bash:将字符串拆分为三重奏和四重奏

Bash:将字符串拆分为三重奏和四重奏

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)(needs extendedglob) 导致匹配项存储在 中$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

相关内容