编辑 sub-shell 中的全局变量

Edit global variable in sub-shell

在以下脚本中,我使用 bash 检查用户是否拥有自己的主目录作为 CIS CentOS 8 基准测试 (6.2.8) 的一部分。

#!/bin/bash
grep -E -v '^(halt|sync|shutdown)' /etc/passwd | awk -F: '( != "'"$(which nologin)"'" &&  != "/bin/false") { print  " "  }' | while read user dir; do
 if [ ! -d "$dir" ]; then
        echo "The home directory ($dir) of user $user does not exist."
 else
        owner=$(stat -L -c "%U" "$dir")
        if [ "$owner" != "$user" ]; then
                echo "The home directory ($dir) of user $user is owned by $owner."
        fi
 fi
done

如果使用全局变量没有错误,我正在尝试打印一些东西。以下是我的尝试:

correct=true
grep -E -v '^(halt|sync|shutdown)' /etc/passwd | awk -F: '( != "'"$(which nologin)"'" &&  != "/bin/false") { print  " "  }' | while read user dir; do
 if [ ! -d "$dir" ]; then
        echo "The home directory ($dir) of user $user does not exist."
        correct=false
 else
        owner=$(stat -L -c "%U" "$dir")
        if [ "$owner" != "$user" ]; then
                echo "The home directory ($dir) of user $user is owned by $owner."
                correct=false
        fi
 fi
done
if [ "$correct" = true ]; then
        echo "Non-compliance?: No"
        echo "Details: All users own their home directories."
        echo
fi

但是,全局变量 correct 不会改变,无论 while 循环中发生什么,因为它在多个子 shell 中。 我阅读了相关内容并注意到人们使用“此处字符串”,因此 while 循环不会在子 shell 中。但是,就我而言,我有多个管道(甚至可能为其他脚本添加更多管道),所以我真的不知道如何让它做我想做的事。

如何从循环中获取结果信息 这样我就可以在循环完成后显示摘要信息 当循环在子 shell?

中执行时

这个How is return handled in a function while loop?,一些重写导致了这个脚本,可以return false:

#!/bin/bash

tmp=/tmp/$$
grep -E -v '^(halt|sync|shutdown)' /etc/passwd | 
   grep -E -v '/bin/false$|nologin$' | 
   awk -F: '{ print , }' > $tmp

correct=true

while read user dir; do
  #echo $user $dir
  if [ ! -d $dir ];
  then
     echo "Dir ($dir) does not exist"
     correct=false
  fi
  owner=$(stat -L -c "$user" "$dir")
  if [ "$owner" != "$user" ];
  then
     echo "home directory for user ($user) is owned by ($owner)"
     correct=false
  fi
done < $tmp

rm $tmp

echo "CORRECT: $correct"
  1. 您的 code/design 在极端情况下失败了: 用户 Horatio Altman 的用户名可能为 haltman。 您的代码会忽略他,因为他的名字以 halt 开头。 你应该使用正则表达式 '^(停止|同步|关闭)<b>:</b>'。 请注意末尾的冒号——这将只匹配具有 halt 的行, syncshutdown 作为 : 分隔文件中的第一个字段。
  2. awk 是一个非常强大的程序。 您几乎不需要将它与 grepsed 之类的东西结合起来; awk 可以做(几乎?)他们可以做的任何事情。 在您的脚本中,我们可以消除 grep 并将halt / sync / shutdown逻辑放入awk
    awk -F: '( != "halt" &&  != "sync" &&  != "shutdown" &&  != "'"$(which nologin)"'" &&  != "/bin/false") { print  " "  }' /etc/passwd |
            while read user dir; do
                      ︙
    
    因为 </code> 表示第一个冒号之前的所有内容(因为我们有 <code>-F:) 我们正在做简单的字符串相等性检查 (而不是正则表达式匹配), 我们不需要开头的 ^ 或结尾的 :。 另请注意,我们可以在 | 之后添加 Enter 不需要反斜杠。
  3. 三引号乱七八糟;很难阅读, 而且很容易出错,尤其是当你在一年后编辑剧本时。 将信息注入 awk 脚本的一种更简洁的方法是使用变量:
    awk -F: -v nl="$(which nologin)" \
                '( != "halt" &&  != "sync" &&  != "shutdown" &&  != nl &&  != "/bin/false") { print  " "  }' /etc/passwd |
            while read user dir; do
                      ︙
    
    在这里,我创建了一个名为 nl 的 awk 变量 值为 $(which nologin), 然后在与 </code> 的比较中使用该变量。 (另外,为了便于阅读,我用反斜杠打破了那条很长的线。)</li> <li>好的,现在我要开始认真回答问题了。 如您所知,脚本的问题在于 它在子 shell 中设置 <code>correct 变量 然后尝试在子外壳之外访问它。 一种解决方案是移动访问变量的语句 进入子外壳。 你可能认为这是不可行的, 因为子外壳是 while 循环, 并且您不想在循环内打印成功消息。 诀窍是强制使用更大的子外壳:
    correct=true
    awk -F: -v nl="$(which nologin)" \
                '( != "halt" &&  != "sync" &&  != "shutdown" &&  != nl &&  != "/bin/false") { print  " "  }' /etc/passwd |
        (
            while read user dir; do
                    if [ ! -d "$dir" ]; then
                            echo "The home directory ($dir) of user $user does not exist."
                            correct=false
                    else
                            owner=$(stat -L -c "%U" "$dir")
                            if [ "$owner" != "$user" ]; then
                                    echo "The home directory ($dir) of user $user is owned by $owner."
                                    correct=false
                            fi
                    fi
            done
            if [ "$correct" = true ]; then
                    echo "All users own their home directories."
            fi
        )
    
    我添加了括号以强制包含一个子外壳 while 循环和 if-then 语句。 为了便于阅读,我将括号放在不同的行中; 您可以将它们移到上一行或下一行 如果您想尽量减少行数。 (下一个代码块演示了这一点。)
  4. 但是如果您想使用 $correct 的值怎么办? 在脚本中的某个时间点? 您不想将大的、不相关的代码块移动到子 shell 中 这样你就可以使用这个技巧了。 好吧,我的下一个技巧是将信息传递出子外壳 通过使用它的退出状态:
    #!/bin/bash
    correct=true
    awk -F: -v nl="$(which nologin)" \
                '( != "halt" &&  != "sync" &&  != "shutdown" &&  != nl &&  != "/bin/false") { print  " "  }' /etc/passwd |
            (while read user dir; do
                    if [ ! -d "$dir" ]; then
                          ︙
                    fi
            done
            if [ "$correct" = true ]; then
                    exit 0
            else
                    exit 1
            fi)
    if [ "$?" = 0 ]; then
            correct=true
    else
            correct=false
    fi
              ︙                         # Possibly much later in the script.
    if [ "$correct" = true ]; then
            echo "All users own their home directories."
    fi
    
    理论上,退出代码的范围可以从 0 到 255, 尽管最好将自己限制在 0-125 范围内。 请参阅 What is the min and max values of exit codes in Linux?