如何通过 SED 适当地传递关联数组值?

How do I appropriately pass associated array values through SED?

我正在创建一个凯撒密码,如果字母表被颠倒,它会用匹配的字母替换单词中的字母。样本是 "abcdefghijklmnopqrstuvwxyz"。以下代码的输出生成 "abcdefghijklmmlkjihgfedcba"。所需的输出是反向的字母表,但是一旦编辑器到达中点,它就会反向返回而不是继续到最后。

declare -A origin

x=({a..z})
z=({z..a})

for i in {0..25}
do
    origin[${x[i]}]=${z[i]}
done

for x in "${!origin[@]}"
do
    sed -i 's/'${x}'/'${origin[${x}]}'/g' test.txt
done

你传递关联数组的方法是正确的,但是凯撒密码的逻辑是错误的。

因为对于 for 循环的每次迭代,sed 命令都会更改输入文件中的一些字符。该字符可以是输入文件中的原始字符,也可以是先前由较早的 sed 更改的字符。因此 for 循环实际上会进行多次转换而不是进行一次转换。

例如

考虑一个输入文件

$ cat test
a z

现在一个小的格式化脚本将是

for x in "${origin[@]}"; do     sed -i  "s/$x/${origin[$x]}/g" test; echo "$x ${origin[$x]}";  done; 
z a
y b
x c
w d
v e
..
..

在第一次迭代中,sed 会将 z 更改为 a。现在输入文件是

$cat test
a a

现在,在第 25 次迭代时,$x 将变为 a,这会将输入中的 a 都转换为 z

$ cat test
z z

备选方案

可以使用 tr 作为

编写替代解决方案
$ a=$(echo {z..a} | tr -d " ")

$ b=$(echo {a..z} | tr -d " ")

$ echo {a..z} | tr $b $a
z y x w v u t s r q p o n m l k j i h g f e d c b a

为什么有效?

这里的字符是从输入中读取的,并用 tr 参数中的相应字符进行更改,这确保单个字符只更改一次。

不要忘记 bash 中的 字符索引 。在您的脚本中,不需要前 2 个 索引数组 x & y。示例:

declare -A origin

x=abcdefghijklmnopqrstuvwxyz
z=zyxwvutsrqponmlkjihgfedcba

for i in {0..25}; do
    origin[${x:i:1}]=${z:i:1}
done

没有什么比用 origin[{a..z}] 替换 {z..a} 并得到熟悉的结果更好的了?例如,仅查看 firstlast 迭代。在 第一次迭代 中,您将所有 a 替换为 z。然后在 最后一次迭代 中,您再次将所有 z(包括您之前在第一次迭代中替换的 a->z )替换为 a' s 再次 -- 有效撤消您的更改。

一个更好的例子是看字母表的中点m->n

x=abcdefghijklmnopqrstuvwxyz
z=zyxwvutsrqponmlkjihgfedcba
              ||

当您的迭代达到 m 时,您将所有 m 替换为 n。然后在下一次迭代中,将 n 替换为 m

你可以看到这看起来像是只有一半的替换受到影响。到达原点的中点后,任何替换只发生 一次,因为您不再遇到已经替换的字母。

使用之前发布的 tr 的解决方案看起来是您最好的选择之一。

我找到了解决问题的方法。谢谢两位的帮助!

#!/bin/bash

#Retrieve the desired shift from user
echo "What number do you want to use for the shift?"
read num

#Create an array of all letters
x=({a..z})

#Take user input and use to create the cipher array
case "$num" in
    0)
    y=({a..z})
    ;;
    1)
    y=({{b..z},a})
    ;;
    2)
    y=({{c..z},a,b})
    ;;
    3)
    y=({{d..z},a,b,c})
    ;;
    4)
    y=({{e..z},a,b,c,d})
    ;;
    5)
    y=({{f..z},{a..e}})
    ;;
    6)
    y=({{g..z},{a..f}})
    ;;  
    7)
    y=({{h..z},{a..g}})
    ;;
    8)
    y=({{i..z},{a..h}})
    ;;
    9)
    y=({{j..z},{a..i}})
    ;;
    10)
    y=({{k..z},{a..j}})
    ;;
    11)
    y=({{l..z},{a..k}})
    ;;
    12)
    y=({{m..z},{a..l}})
    ;;
    13)
    y=({{n..z},{a..m}})
    ;;
    14)
    y=({{o..z},{a..n}})
    ;;
    15)
    y=({{p..z},{a..o}})
    ;;
    16)
    y=({{q..z},{a..p}})
    ;;
    17)
    y=({{r..z},{a..q}})
    ;;
    18)
    y=({{s..z},{a..r}})
    ;;
    19)
    y=({{t..z},{a..s}})
    ;;
    20)
    y=({{u..z},{a..t}})
    ;;
    21)
    y=({{v..z},{a..u}})
    ;;
    22)
    y=({{w..z},{a..v}})
    ;;
    23)
    y=({{x..z},{a..w}})
    ;;
    24)
    y=({{y..z},{a..x}})
    ;;
    25)
    y=({{z..z},{a..y}})
    ;;
    *)
    echo "Sorry, you must use a shift from 0 to 25."
    ;;
esac

#create the string variables for manipulation
fromset=""
toset=""

#place the alphabetic arrays into the atring variables
for i in {0..25}
do
    fromset="$fromset${x[i]}"
    toset="$toset${y[i]}"
done

#Use sed text transformations to alter given files
sed "y/$fromset/$toset/" original.txt > encoded.txt
sed "y/$toset/$fromset/" encoded.txt > decoded.txt