【问题标题】:bash for loop keeps adding ontop of the variablebash for 循环不断在变量之上添加
【发布时间】:2019-11-04 01:23:24
【问题描述】:

循环是添加变量而不是重写它。我不知道为什么会这样。 如果有人可以帮助我,那么真的非常感谢我,我是 bash 的新手,我已经在这里工作了几个小时了......

for (( i=1; i<=4; i++ ))
do
    echo User:$(tail -n $i /etc/passwd | cut --delimiter=":" --fields="1")
    echo
    echo
done

输出变成了

User:art101c40
User:art101c39 art101c40
User:art101c38 art101c39 art101c40
User:art101c37 art101c38 art101c39 art101c40

应该是这样的

User: art101c40
User: art101c39
User: art101c38
User: art101c37

【问题讨论】:

  • Tail 从文件底部抓取 -n 行,然后子shell $() 连接它的输出。所以输出是预期的。尝试描述您要解决的问题,以便我们更好地帮助您。
  • 是的,同意,我想如果你检查/etc/passwd (less, vi, cat) 你会看到最后4行包含art101c37.....。祝你好运。
  • 我正在尝试制作一个循环,其中列出了用户名用户:名称用户:名称但我这里的循环给了我用户:名称名称用户:名称名称名称它不断添加到变量上,而不仅仅是覆盖它。我发现可行的唯一方法是使用 printf 而不是 echo。

标签: linux bash loops for-loop


【解决方案1】:

tail -n 从文件末尾获取$i 行,所以在第一个循环中你得到一行,在第二个循环中你得到两行,等等。你想要做的是得到最后四行并遍历它们,您可以使用 while read 循环来完成:

tail -n 4 /etc/passwd |
    cut -d: -f1 |
    while read -r user  # "-r" prevents backslashes causing problems
do
    echo "User: $user"
done

或者您可以使用 AWK 替换 cutwhile read 循环:

tail -n 4 /etc/passwd |
    awk -F: '{print "User:", $1}'

(从 Gordon Davisson 改进)

【讨论】:

  • 你也可以去掉cut,使用awk -F: '{print "User:", $1}'只打印第一个字段。
  • 那么我猜使用for循环就行不通了?我也喜欢你如何将它们从低数字翻转到更高数字。因为我还有其他东西要添加。大声笑而且我不确定如何添加到您的编码中,因为它们是我需要获取的其他一些字段。
  • @SnakZ 您可以使用tail -n $i | head -n 1 进行for循环,但这样做更容易,尤其是在您希望它们按升序排列的地方。
  • 如果你不难做到,你能告诉我如何使用 for 循环吗?此外,我仍然很困惑为什么它做了它所做的事情。我曾经知道一些php编码。根据我的理解,它应该有效。我想我只是想弄清楚为什么它不起作用。另外,我将如何将其他项目添加到循环中?如果你不介意
【解决方案2】:

@wjandrea 已经解释了如何做到这一点;我将尝试解释为什么原来的不起作用。我还将说明一种重要的调试技术:如果一个复杂的进程没有按照您的意愿运行,请将其拆开并单独运行,看看它们在做什么。

在这种情况下,循环的第一次迭代会按预期工作(它会打印“User:art101c40”),所以我们不必担心。让我们看一下第二次迭代,其中i=2。替换该值后,它将运行命令:

echo User:$(tail -n 2 /etc/passwd | cut --delimiter=":" --fields="1")

所以,让我们浏览一下这些部分,看看它们做了什么。首先,它运行tail -n 2 /etc/passwd,它将打印/etc/passwd 的最后两行。有点像这样:

art101c39:*:10139:99:Art 101 class 39:/home/art101c39:/bin/bash
art101c40:*:10140:99:Art 101 class 40:/home/art101c39:/bin/bash

请注意,它会打印最后两行(tail -n 2 所做的),因此它同时打印了 art101c39 和 art101c40。这是整个问题的根源。

但是让我们继续追踪它的行为。该输出通过cut --delimiter=":" --fields="1" 传输,打印:

art101c39
art101c40

这被$( ) 捕获,用作echo 的参数。每个用户名都在一个单独的行上,因此它们用换行符分隔,但 $( ) 不在双引号中,因此它需要进行分词。分词将其分解为基于空格(通常是空格、制表符和换行符)的“单词”,因此每个用户名只是一个单独的单词。本质上,它变成了“art101c39”和“art101c40”。如果其中任何一个有任何文件名通配符,shell 就会尝试将它们转换为匹配文件的列表;但是没有通配符,所以这不会发生。然后将这些“单词”添加到echo 的参数中,给出:

echo User:art101c39 art101c40

echo 接收两个参数,“User:art101c39”和“art101c40”。与往常一样,它将它们粘在一起并在它们之间留一个空格并打印结果。

类似的事情发生在第三次和第四次迭代中,tail 打印三个然后四个帐户,所有这些帐户都经过其余处理。

您可以通过将head 添加到管道中直接解决此问题:tail -n $i /etc/passwd | head -n 1 | ...。这样,tail 将打印最后的$i 行,head 将消除除第一行之外的所有行(文件末尾的$ith)。但是这种方法往往效率低下并且(IMO)很笨拙。它涉及通读整个文件以从一行中挑选出一个字段。对于四行(帐户),您需要将整个文件读取四次。如果您要从每个用户中挑选出两个字段,那就意味着读取文件八次。

通常有一种更简单、更好的方法可以做到这一点,但这取决于实际目标是什么。通常,您会运行一次tail,然后处理该输出的常用方法包括:让awk -F: 完成所有工作;管道连接到while IFS=: read -r user pw uid gid fullname homedir shell; do 循环;使用readarray -t 将这些线拉到一个shell 数组中,您可以在其中直接使用它们;等等

【讨论】:

  • 这解释了一切,我非常感谢你,也非常感谢那边的@wjandrea 和你们其他人帮助我的强迫症,试图找出为什么这不起作用。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2020-04-05
  • 2014-01-07
相关资源
最近更新 更多