BASH - 随机排列多行字符串中的字符

BASH - Shuffle characters in strings from several rows

我有一个具有以下结构的文件 (filename.txt):

>line1
ABC
DEF
GHI
>line2
JKL
MNO
PQR
>line3
STU
VWX
YZ

我想打乱>开头的字符串中的字符。输出(例如)如下所示:

>line1
DGC
FEI
HBA
>line2
JRP
OKN
QML
>line3
SZV
YXT
UW

这就是我尝试为每个 >line[number]: ruby -lpe '$_ = $_.chars.shuffle * "" if !/^>/' filename.txt 打乱字符的方法。该命令有效(请参阅我的 post )但它会逐行随机播放。我想知道如何修改命令以在每个 >line[number]all 字符串之间随机排列字符)。使用 ruby 不是必需的。

#!/bin/bash

# echo > output.txt         # uncomment to write in a file output.txt

mix(){
    {
        echo "$title"
        line="$( fold -w1 <<< "$line"  | shuf  )"
        echo "${line//$'\n'}" | fold -w3
    }  # >> output.txt         # uncomment to write in a file output.txt
    unset line
}

while read -r; do
    if [[ $REPLY =~ ^\> ]]; then
        mix
        title="$REPLY"
    else
        line+="$REPLY"
    fi
done < filename.txt
mix       # final mix after loop's exit, otherwise line3 will be not mixed

exit

编辑 gniourf-gniourf

的评论

首先我们要解决的问题是:如何将多行中的所有字符打乱:

echo -e 'ABC\nDEF\nGHI' |grep -o . |shuf |tr -d '\n'
GDAFHEIBC

而且,我们还需要一个数组来记录原始字符串中每一行的长度。

s=GDAFHEIBC
lens=(3 3 3)
start=0
for len in "${lens[@]}"; do
    echo ${s:${start}:${len}}
    ((start+=len))
done
GDA
FHE
IBC

所以,原点多行:

ABC
DEF
GHI

已改组为:

GDA
FHE
IBC

现在,我们可以做我们的工作了:

lens=()
string=""

function shuffle_lines {
    local start=0
    local shuffled_string=$(grep -o . <<< ${string} |shuf |tr -d '\n')
    for len in "${lens[@]}"; do
        echo ${shuffled_string:${start}:${len}}
        ((start+=len))
    done
    lens=()
    string=""
}

while read -r line; do
    if [[ "${line}" =~ ^\> ]]; then
        shuffle_lines
        echo "${line}"
    else
        string+="${line}"
        lens+=(${#line})
    fi
done <filename.txt

shuffle_lines

示例:

$ cat filename.txt
>line1
ABC
DEF
GHI
>line2
JKL
MNO
PQR
>line3
STU
VWX
YZ
>line4
0123
456
78
9
$ ./solution.sh
>line1
HFG
BED
AIC
>line2
JOP
KMQ
RLN
>line3
UVW
TYZ
XS
>line4
1963
245
08
7

首先创建一个测试文件。

str =<<FINI
>line1
ABC
DEF
GHI
>line2
JKL
MNO
PQR
>line3
STU
VWX
YZ
FINI

File.write('test', str)
  #=> 56

现在读取文件并执行所需的操作。

result = File.read('test').split(/(>line\d+)/).map do |s|
  if s.match?(/\A(?:|>line\d+)\z/)
    s
  else
    a = s.scan(/\p{Lu}/).shuffle
    s.gsub(/\p{Lu}/) { a.shift }
  end
end.join
  # ">line1\nECF\nHIA\nGBD\n>line2\nJNP\nKLR\nOQM\n>line3\nTXY\nUZV\nSW\n"

puts result
>line1
ECF
HIA
GBD
>line2
JNP
KLR
OQM
>line3
TXY
UZV
SW

要从命令执行此操作,请将代码转换为字符串,语句之间用分号分隔。

ruby -e "puts (File.read('test').split(/(>line\d+)/).map do |s|; if s.match?(/\A(?:|>line\d+)\z/); s; else; a = s.scan(/\p{Lu}/).shuffle; s.gsub(/\p{Lu}/) { a.shift }; end; end).join"

步骤如下

a = File.read('test')
  #=> ">line1\nABC\nDEF\nGHI\n>line2\nJKL\nMNO\nPQR\n>line3\nSTU\nVWX\nYZ\n"
b = a.split(/(>line\d+)/)
  #=> ["", ">line1", "\nABC\nDEF\nGHI\n", ">line2", "\nJKL\nMNO\nPQR\n",
  #    ">line3", "\nSTU\nVWX\nYZ\n"]

请注意,作为 split 参数的正则表达式将 >line\d+ 置于捕获组中。否则,">line1"">line2"">line3" 将不会包含在 b.

c = b.map do |s|
  if s.match?(/\A(?:|>line\d+)\z/)
    s
  else
    a = s.scan(/\p{Lu}/).shuffle
    s.gsub(/\p{Lu}/) { a.shift }
  end
end
  #=> ["", ">line1", "\nEAC\nIHB\nDGF\n", ">line2", "\nKQJ\nROL\nMPN\n",
  #    ">line3", "\nSUY\nXTV\nZW\n"]
c.join
  #=> ">line1\nEAC\nIHB\nDGF\n>line2\nKQJ\nROL\nMPN\n>line3\nSUY\nXTV\nZW\n"

现在更仔细地考虑 c 的计算。 b 的第一个元素被传递给块,块变量 s 被设置为其值:

s = ""

然后我们计算

s.match?(/\A(?:|>line\d+)\z/)
  #=> true

所以 "" 是从块中 return 编辑的。正则表达式可以表示如下。

/
\A          # match the beginning of the string
(?:         # begin a non-capture group
            # match an empty space
  |         # or
  >line\d+  # match '>line' followed by one or more digits
)           # end non-capture group
\z          # match the end of the string
/x          # free-spacing regex definition mode.

在 non-capture 组中匹配了一个空的 space。

然后将 b 的下一个元素传递给块。

s = ">line1"

再次

s.match?(/\A(?:|>line\d+)\z/)
  #=> true

所以 s 是来自区块的 return。

现在 b 的第三个元素被传递给块。 (最后,有趣的事情。)

s = "\nABC\nDEF\nGHI\n"
d = s.scan(/\p{Lu}/)
  #=> ["A", "B", "C", "D", "E", "F", "G", "H", "I"]
a = d.shuffle
  #=> ["D", "C", "G", "H", "B", "F", "I", "E", "A"]
s.gsub(/\p{Lu}/) { a.shift }
  #=> "\nDCG\nHBF\nIEA\n"

其余计算类似。