从字符串中提取每第 n 个字符

从字符串中提取每第 n 个字符

我正在尝试找出解决方案问题。到目前为止,我解决这个问题的方法如下。

  • 将所有字符附加在一起以使其成为一个长字符串。
  • 完成上述步骤后,删除所有空格或制表符空格,这样我们就只剩下一个大字符串了。

我能够使用以下命令建立上述步骤。

column -s '\t' inputfile | tr -d '[:space:]'

所以对于这样的输入文件,

1   0   0   0   0   0

0   1   1   1   0   0

应用上述命令后,我的值如下:

100000011100

现在,在这个大字符串中,我尝试应用如下方法。

提取每 6字符(如原始 OP 所需),并将其附加到数组元素直到字符串末尾。

所以基本上,通过上述步骤,我尝试将数组元素创建为,

101和第 7字符)、01(第 2第 8字符)、01(第 3第 9字符)、01(第 4第 10字符)、00(第 5第 11字符)、006和 12 个字符)第一个字符)。

所以我的问题是,如何提取每 n 个字符,以便将它们添加到数组中以进一步进行? (在本例中,n=6)。

答案1

两行

这是bash一个生成bash数组的纯解决方案:

s="100000011100"
array=($(
    for ((i=0; i<${#s}-6; i++))
    do
        echo "${s:$i:1}${s:$((i+6)):1}"
    done
    ))
echo "${array[@]}"

这会产生与问题中所示相同的输出:

10 01 01 01 00 00

这里的关键要素是 bash 的使用子串扩展。 Bash 允许从变量中提取子字符串,例如parametervia ${parameter:offset:length}。在我们的例子中,偏移量由循环变量确定i,长度始终为1

任意数量线路的通用解决方案

例如,假设我们的原始字符串有 18 个字符,我们要提取 i 从 0 到 5 的第 i 个、第 i+6 个和第 i+12 个字符。那么:

s="100000011100234567"
array=($(
    for ((i=0; i<6; i++))
    do
        new=${s:$i:1}
        for ((j=i+6; j<${#s}; j=j+6))
        do 
            new="$new${s:$j:1}"
        done
        echo "$new"
    done
    ))

echo "${array[@]}"

这会产生输出:

102 013 014 015 006 007

相同的代码可以扩展到任意数量的 6 字符行。例如,如果s有三行(18 个字符):

s="100000011100234567abcdef"

然后,输出变为:

102a 013b 014c 015d 006e 007f

答案2

使用perl

$ echo 100000011100 | perl -nle '
    for ($i = 0; $i < length()/2; $i++) {
        print substr($_,$i,1), substr($_,$i+6,1);
    }
'
10
01
01
01
00
00

它适用于两条线。如果您想处理任意行,您应该直接处理行,而不是构建大字符串。通过此输入:

1   0   0   0   0   0                                                           
0   1   1   1   0   0                                                           
0   0   0   0   0   0

尝试:

$ perl -anle '
    for ($i = 0; $i <= $#F; $i++) {
      push @{$h{$i}}, $F[$i];
    }
    END {
        print @{$h{$_}} for keys %h;
    }
' file
000
010
000
100
010
010

答案3

作为 shell 解决方案,getopts可能是最简单的。问题在于getopts,它是 POSIX 指定的,可以完全按照您的要求进行操作 - 在 shell 循环中处理字节流。我知道这听起来很奇怪,因为如果你在我学到这一点之前像我一样,你可能会想,好吧,哎呀,我以为它应该处理命令行开关。这是事实,但第一件事也是如此。考虑:

-thisisonelongstringconsistingofseparatecommandlineswitches

是的,getopts必须处理这个问题。它必须在循环中逐个字符地拆分该字符,然后将 shell 变量$OPTARG或您通过名称指定的另一个变量中的每个字符返回给您,这一切都取决于您调用它时获得的具体程度。更重要的是,它必须返回 shell 变量中的错误保存其进度当它在 shell 变量中执行时,$OPTIND它可以从停止处继续如果你能以某种方式解决它。它必须在不调用任何子 shell 的情况下完成整个工作。

假设我们有:

arg=$(seq -s '' 1000); set --
while getopts :0123456789 v -"${arg}"
do [ "$((i=$i+1<6?$i+1:0))" -gt 0 ] ||
set "$@" "$v"
done

嗯......我想知道它是否有效?

echo "$((${#arg}/6))" "$#"
482 482

那很好...

eval '
printf %.1s\\n "${arg#'"$(printf %0$((124*6-1))d | tr 0 \?)"'}" "${124}"'
4
4

因此,如您所见,该getopts命令为字符串中的每六个字节完全设置了数组。它不一定是这样的数字 - 甚至也不一定是 shell 安全字符 - 并且您甚至不需要像我上面那样指定目标字符01234565789。我已经在很多 shell 中反复测试过,它们都可以正常工作。有一些怪癖 -bash如果第一个字符是空白字符,则会丢弃它 -dash接受:冒号作为指定参数,即使它是唯一 POSIX 明确禁止的。但这都不重要,因为即使它返回错误,getopts仍然会存入当前 opt char 的值$OPTARG(由分配给您指定的 opt 变量的 ? 表示)否则显式取消设置,$OPTARG除非您声明选项应该有参数。空白是一件好事——它只会丢弃一个领导space,这非常好,因为在处理未知值时,您可以执行以下操作:

getopts : o -" $unknown_value"

...开始循环,而不会有任何第一个字符实际位于您接受的 args 字符串中的危险 - 这将导致立即getopts将整个内容$OPTARG作为参数插入。

这是另一个例子:

OPTIND=1
while getopts : o -" $(dd if=/dev/urandom bs=16 count=1 2>/dev/null)"                         
do printf '\\%04o' "'$OPTARG"; done  

\0040\0150\0071\0365\0320\0070\0161\0064\0274\0115\0012\0215\0222\0271\0146\0057\0166

$OPTIND=1在第一行设置是因为我刚刚使用过getopts,并且在您重置它之前,它期望下一次调用从它停止的地方继续 -"${arg2}"换句话说,它想要。但我不想付出,而且我现在正在做另一件事,所以我通过重置来让它知道$OPTIND什么时候可以开始。

在这个中我使用了zsh- 它不会对前导空格提出异议 - 因此第一个字符是八进制 40 - 空格字符。不过,我通常不getopts以这种方式使用 - 我通常用它来避免对每个字节执行 a 操作write(),并将其输出(来自变量)分配给另一个 shell 变量,就像我在上面所做的那样set。然后,当我准备好时,我可以获取整个字符串,而当我这样做时,通常会删除第一个字节。

答案4

sed我首先想到的是。

$ echo 1234567890abcdefghijklmnopqrstuvwxyz | sed 's/.\{5\}\(.\)/\1/g'
6bhntz

匹配 5 个字符,捕获第 6 个字符,然后将其全部替换为捕获的字符。

然而,如果字符串的长度不是 6 的精确倍数,则会出现问题:

$ echo 1234567890abcdefghijklmnopqrstuvwxy | sed 's/.\{5\}\(.\)/\1/g' 
6bhntuvwxy

但我们可以通过sed稍微改变一下来解决这个问题:

$ echo 1234567890abcdefghijklmnopqrstuvwxy | sed 's/.\{1,5\}\(.\{0,1\}\)/\1/g'
6bhnt

由于正则表达式的贪婪本质,可变长度匹配将尽可能匹配,如果没有剩余内容可供捕获,则不会捕获,并且字符将被删除。

相关内容