BASH:读取数组并选择字段

BASH: Reading Into An Array and Selecting Fields

我有两个问题...

首先,我有一组已定义的信息,我正在回显 (echo -e) 到文件“/tmp/replacements” ... (t运行分类版本):

"RU_SIT1_CA-dev.ext.foo.bar.com,123.456.789.000,123.456.789.000\nDEV1_DA-dev.ext.foo.bar.com,123.456.789.000,123.456.789.000\nDEV1_DP-dev.ext.foo.bar.com,123.456.789.000,123.456.789.000\nFTDEV1_DA-dev.ext.foo.bar.com,123.456.789.000,123.456.789.000\nFTDEV1_DP-dev.ext.foo.bar.com,123.456.789.000,123.456.789.000\nFTSIT1_DA-dev.ext.foo.bar.com,123.456.789.000,123.456.789.000\nFTSIT1_DP-dev.ext.foo.bar.com,123.456.789.000,123.456.789.000\nSIT1_CA-dev.ext.foo.bar.com,123.456.789.000,123.456.789.000\nDEVX1_CA-dev.ext.foo.bar.com,123.456.789.000,123.456.789.000\nFTDEV1_CA-dev.ext.foo.bar.com,123.456.789.000,123.456.789.000\nDEVX1_DA-dev.ext.foo.bar.com,123.456.789.000,123.456.789.000\nDEVX1_DP-dev.ext.foo.bar.com,123.456.789.000,123.456.789.000\nSIT1_DA-dev.ext.foo.bar.com,123.456.789.000,123.456.789.000\nSIT1_DP-dev.ext.foo.bar.com,123.456.789.000,123.456.789.000\nDEV1_CA-dev.ext.foo.bar.com,123.456.789.000,123.456.789.000\nRU_SIT2_CA-dev.ext.foo.bar.com,123.456.789.000,123.456.789.000\nDEV2_DA-dev.ext.foo.bar.com,123.456.789.000,123.456.789.000\nDEV2_DP-dev.ext.foo.bar.com,123.456.789.000,123.456.789.000\nFTDEV2_DA-dev.ext.foo.bar.com,123.456.789.000,123.456.789.000\nFTDEV2_DP-dev.ext.foo.bar.com,123.456.789.000,123.456.789.000\nFTSIT2_DA-dev.ext.foo.bar.com,123.456.789.000,123.456.789.000\nFTSIT2_DP-dev.ext.foo.bar.com,123.456.789.000,123.456.789.000\nSIT2_CA-dev.ext.foo.bar.com,123.456.789.000,123.456.789.000" > "/tmp/replacements"

我试图将该文件读入数组:

read -a OLDIP "/tmp/replacements"
echo "${OLDIP[@]}"

当脚本执行时,shell 只是挂起而不返回。没有什么。我不明白为什么。 "Bash Cookbook"、"ShellCheck" 和 "Man page" 都说 read -a 应该有效,伴随 echo "${OLDIP[@]".

我也试过用 WHILE 循环来做:

while IFS= read -a line
do
    line=$REPLY
done <<< "/tmp/replacements"

echo "${line[@]}"

这次执行时,shell并没有挂起,只是没有输出任何东西。

如果我这样做 "Dirty Way"(笑),它会起作用:OLDIP=$(cat "/tmp/replacements") ...因为我还在学习,所以我尽可能地尝试使用 "Best Practices",并且从我所看到的...虽然这可能没有错,但肯定有 更好 的方法来做到这一点。 (另外,当我想存储 LINES 时,我认为会将每个 WORD 分配给数组中的元素)

这是第一个问题...现在是第二个...

我遇到的第二个问题是如何 select 数组中的一个字段?

我正在尝试替换“/etc/hosts”中的 IP 地址...我想 运行 脚本并说:

# search "/etc/hosts" for an IP we have in the array
# if a match is found, perform a SED to replace that
# IP with the IP found in position #3 of that line
# (LB_FQDN,OLDIP,NEWIP)

for i in "${line[@]}"
    do
        grep -q "$i" "/etc/hosts"
            if [[ "$?" -eq "0" ]] #when grep is quiet (-q) exit status 0 means a match was found.
                then
                    sed -i "s/$i/THE-MATCHING-IP-WAS-FOUND,-REPLACE-IT-WITH-THE-SECOND-IP-IN-THAT-SAME-LINE/g" "/etc/hosts"
            fi
    done

假设我可以让我的数组实际填充“正确的方式”,我将非常感谢关于如何搜索“/etc/hosts/”的一些指导在字段 #2 中找到 IP,如果找到,将其替换为同一行字段 #3 中的 IP。

非常感谢您的帮助。

干杯! -Alex.

您可以用这个 awk 脚本替换整个问题:

#!/usr/bin/awk -f
NR == FNR {
  z[FNR][1]
  split([=10=], z[FNR], ",")
  next
}
 == z[FNR][2] {
   = z[FNR][3]
}
1

然后运行像这样:

awk /tmp/replacements /etc/hosts

如果您想继续使用 read 而不是 mapfilereadarray,您可以这样做:

read -d '' -r -a A < replacements

echo "${A[@]}"
read -a OLDIP <<< $(cat /tmp/replacements)
echo "${OLDIP[@]}"

for item in ${OLDIP[@]}
do
   item=( $(echo $item | tr ',' ' ') )
   sed -i "s/${item[1]}/${item[2]}/" /etc/hosts  
done

我认为可以。这是假设行数据中从来没有白色 space。如果有,则必须使用 IFS 并在循环期间将其重置为 ,然后在下一个循环之前设置为原始值。

解释,重新评论请求:

  1. “<<<”与“<”或“<<”有何不同 A. 发送变量中包含的数据,在本例中为:$(cat /tmp/replacements),发送给任何将使用它的对象。

我写了这么多 bash 以至于老实说我不再使用某些难以阅读或给出不可靠结果或不一致的结构。例如,在你的身上,我先尝试了 < /tmp/replacements 但它没有用。所以我只是切换到我一直使用的和将始终有效的东西,我注意到在我的一个大 bash 程序中有两个地方使用 < 来获取文件中的某些数据,但我没有写那些线,永远无法真正弄清楚它们是如何工作的,或者为什么,哈哈。

> 表示:写入文件,如:echo yes > file.txt,并创建该文件,覆盖内容。

>> 表示:追加到应该已经存在的文件。

< 表示从文件中读取,但在某种特殊意义上,它不会按您期望的方式或按您期望的方式执行。我从不使用这个,几乎从不。

<< 不存在。

我的 io 规则很简单: > 写入某物,并首先创建它/将其设置为空。 当然,除了像 echo fred >/dev/null 这样的东西,它只会让它消失,stderr/stdout 重定向等。 >> 附加到文件 <<< 要读取任何内容,如果是文件,我会抓取它。

http://tldp.org/LDP/abs/html/x17837.html

A here string can be considered as a stripped-down form of a here document.
It consists of nothing more than COMMAND <<< $WORD,
where $WORD is expanded and fed to the stdin of COMMAND.

<<< 就像管道一样,但我相信它更有效率。

 cat /etc/hosts | command
 # works much like:
 command <<< $(cat /etc/hosts)

除非该函数本身处理文件,例如 grep something filename、sed -i 's/stuff/stuff/' filename 等

http://www.tldp.org/LDP/abs/html/io-redirection.html

如果您不熟悉该站点,请将其加入书签,它是最好的 BASH 资源之一。

0< FILENAME
< FILENAME
  # Accept input from a file.
  # Companion command to ">", and often used in combination with it.
  #
  # grep search-word <filename

即使这个例子也有点奇怪,因为你真的会这样做:

grep search-word filename

我无法诚实地解释 bash 对 < filename 的作用,因为我从不使用该结构,它是不可预测的,我更喜欢 bash 是可预测的,所以我把它弄糊涂了供我使用,顺便说一句,效果非常好。例如,在您的情况下,它创建了一个包含 1 个项目的数组。

2:我看到我们将 "item" 的值设置为它在数组中找到的结果......我们如何确定我们的数组有 LINES 而不是 WORDS?

item=( $(echo $item | tr ',' ' ') )

item=( word word word word) 是这个的结果,每个结果数组都是从文件中通过 ',' 到 ' ' 的翻译分成 'words' 的一行,因此,你有bash 数组使用默认的 bash 内部字段分隔符 IFS of ' '。

http://tldp.org/LDP/abs/html/internalvariables.html

阅读页面重新 IFS。 IFS 无法简单解释,因此除非您有具体问题,否则我会将其留给 tldp 页面进行解释。

这个问题我不是很清楚。我的文件基于给定的用户数据样本,它显然永远不会有 spaces,也就是单词,即它是一行。如果数据可以有 spaces,则需要更多处理。

RU_SIT1_CA-dev.ext.foo.bar.com,123.456.789.000,123.456.789.000\n

例如,是给定的一行。该域不会有 space,IP 1 和 IP 2 不会有 space。我假设这些是动态生成的,所以该行不会有超过 1 个单词,即它是一个没有提供的 spaces 的字符串。

如果需要显式 space 处理,我会在循环中使用 IFS=',' 创建数组,然后从那里继续。但是 tr ',' ' ' 是 fastest/easiest 生成 bash space 分隔数组的方法,bash 默认值。

  1. 此外,我知道 "tr" 用于 TRim 从管道接收的输入,但这是否表示它 trim 将逗号替换为任何内容?

tr用于替换单个字符,而不是trim。可以这样想:tr(anslate),即将 x 转换为 y,在本例中,',' 转换为 ' '。逗号没有被替换为空,它被替换为'',一个space.

man tr:
NAME
       tr - translate or delete characters
DESCRIPTION
       Translate, squeeze, and/or delete characters from standard input, writing to standard output.

tr 只是在 shell.

中将一个字符更改为另一个字符的 fastest/easiest 方法

这就是我不使用 <

的原因
read -a OLDIP <<< $(cat /tmp/replacements);for item in ${OLDIP[@]}; do item=( $(echo $item | tr ',' ' ') );echo ${item[1]}/${item[2]};done

123.456.789.000/123.456.789.000
123.456.789.000/123.456.789.000
123.456.789.000/123.456.789.000
123.456.789.000/123.456.789.000
123.456.789.000/123.456.789.000
123.456.789.000/123.456.789.000
123.456.789.000/123.456.789.000
123.456.789.000/123.456.789.000
123.456.789.000/123.456.789.000
123.456.789.000/123.456.789.000
123.456.789.000/123.456.789.000
123.456.789.000/123.456.789.000
123.456.789.000/123.456.789.000
123.456.789.000/123.456.789.000
123.456.789.000/123.456.789.000
123.456.789.000/123.456.789.000
123.456.789.000/123.456.789.000
123.456.789.000/123.456.789.000
123.456.789.000/123.456.789.000
123.456.789.000/123.456.789.000
123.456.789.000/123.456.789.000
123.456.789.000/123.456.789.000
123.456.789.000/123.456.789.000

但是,使用 <

read -a OLDIP < /tmp/replacements;for item in ${OLDIP[@]}; do item=( $(echo $item | tr ',' ' ') );echo ${item[1]}/${item[2]};done

你得到:

123.456.789.000/123.456.789.000

也就是说,只有一行,第一行或最后一行,可能是最后一行。我相信那里发生的事情是,在读取的每一行都会创建一个新数组 OLDIP,因此您最终只会得到最后一行数组项,而使用 <<< 您会立即吐出整个内容,并且读取需要一次完成所有行并按照您的预期进行,创建包含其中所有行的数组。我测试了这个:

awk '{print [=21=]}' < /tmp/replacements

正如您所期望的那样工作,所以这似乎是特定于 read -a 的内容。老实说,我个人永远不会以这种方式使用读取来生成数组,我会直接使用另一种方法生成数组,但由于那是你问的问题的方法,所以我认为这会很有趣找出它不起作用的原因。

鉴于:

<<< $(cat /tmp/replacements)

将始终毫无例外地完全按照您的预期执行,将整个文件吐回 read -a。

我想这回答了你剩下的问题。

我写了很多荒谬的东西 bash 我不再使用它的任何难以记住、难以理解、难以阅读或难以调试的功能,或者那些不按照您期望的方式行事。