在bash中打印两次的循环

在bash中打印两次的循环,bash,shell,Bash,Shell,我正在编写这个bash脚本,它应该打印出所有从未登录过的用户,并提供对他们进行排序的选项。我已经设法使所有的输入都能正常工作,但是,在打印输出时,我遇到了一些问题。循环如下: for user in $(lastlog | grep -i 'never' | awk '{print $1}'); do grep $user /etc/passwd | awk -F ':' '{print $1, $3}' done 当然,这个循环不会对输出进行排序,但是

我正在编写这个bash脚本,它应该打印出所有从未登录过的用户,并提供对他们进行排序的选项。我已经设法使所有的输入都能正常工作,但是,在打印输出时,我遇到了一些问题。循环如下:

    for user in $(lastlog | grep -i 'never' | awk '{print $1}'); do
            grep $user /etc/passwd | awk -F ':' '{print $1, $3}'
    done

当然,这个循环不会对输出进行排序,但是,从我对shell和shell脚本的有限理解来看,它应该只是在第一个“awk”{print$1}'之后加上一个“| sort”。我的问题是,这个循环的输出至少打印每个用户两次,在某些情况下,打印四次。这是为什么?我如何修复它?

好吧,让我们试着调试它:

for user in $(lastlog | grep -i 'never' | awk '{print $1}'); do
        echo "The user '$user' matches these lines:"
        grep $user /etc/passwd | awk -F ':' '{print $1, $3}'
        echo
done
这将产生:

The user 'daemon' matches these lines:
daemon 1
colord 112

The user 'bin' matches these lines:
root 0
daemon 1
bin 2
sys 3
sync 4
games 5
man 6
(...)
实际上,
colord
的条目确实包含
daemon

colord:x:112:120:colord colour management daemon,,,:/var/lib/colord:/bin/false
                                          ^-- Here
而且
games
条目与
bin
匹配:

games:x:5:60:games:/usr/games:/usr/sbin/nologin
                                    ^-- Here
因此,我们不想在任何地方匹配用户名字符串,而只想从行首到第一个冒号匹配它:

for user in $(lastlog | grep -i 'never' | awk '{print $1}'); do
        echo "The user '$user' matches these lines:"
        grep "^$user:" /etc/passwd | awk -F ':' '{print $1, $3}'
        echo
done
现在每个条目只显示它应该显示的单个条目,所以你可以移除回声并继续


如果您对finesse和polish感兴趣,这里有一个可供选择的解决方案,它可以跨语言设置、奇怪的用户名、网络身份验证、大型列表等高效工作:

LC_ALL=C lastlog | 
   awk -F ' ' '/Never logged in/ {printf "%s\0", $1}' |
   xargs -0 getent passwd | 
   awk -F : '{print $1,$3}' |
   sort

那么,让我们试着调试它:

for user in $(lastlog | grep -i 'never' | awk '{print $1}'); do
        echo "The user '$user' matches these lines:"
        grep $user /etc/passwd | awk -F ':' '{print $1, $3}'
        echo
done
这将产生:

The user 'daemon' matches these lines:
daemon 1
colord 112

The user 'bin' matches these lines:
root 0
daemon 1
bin 2
sys 3
sync 4
games 5
man 6
(...)
实际上,
colord
的条目确实包含
daemon

colord:x:112:120:colord colour management daemon,,,:/var/lib/colord:/bin/false
                                          ^-- Here
而且
games
条目与
bin
匹配:

games:x:5:60:games:/usr/games:/usr/sbin/nologin
                                    ^-- Here
因此,我们不想在任何地方匹配用户名字符串,而只想从行首到第一个冒号匹配它:

for user in $(lastlog | grep -i 'never' | awk '{print $1}'); do
        echo "The user '$user' matches these lines:"
        grep "^$user:" /etc/passwd | awk -F ':' '{print $1, $3}'
        echo
done
现在每个条目只显示它应该显示的单个条目,所以你可以移除回声并继续


如果您对finesse和polish感兴趣,这里有一个可供选择的解决方案,它可以跨语言设置、奇怪的用户名、网络身份验证、大型列表等高效工作:

LC_ALL=C lastlog | 
   awk -F ' ' '/Never logged in/ {printf "%s\0", $1}' |
   xargs -0 getent passwd | 
   awk -F : '{print $1,$3}' |
   sort

您的grep将匹配多行(man将匹配manuel和norman等),将其锚定到行的开头并添加一条线索:

grep "^${user}:" /etc/passwd | awk -F ':' '{print $1, $3}'
更好的选择可能是完全忘记grepping/etc/passwd,使用
id
命令获取用户id:

id=$(id -u "${user}" 2>/dev/null) && printf "%s %d\n" "${user}" "${id}"
如果id命令失败,则不会打印任何内容,或者可以将其修改为:

id=$(id -u "${user}" 2>/dev/null)
printf "%s %s\n" "${user}" "${id:-(User not found)}"

在gnu linux中,我很确定找到的用户id不存在是不可能的,因为lastlog将只报告现有用户,因此第二个示例可能没有意义。

您的grep将匹配多行,(man将匹配manuel和norman等)将其锚定到行的开头并添加一条线索:

grep "^${user}:" /etc/passwd | awk -F ':' '{print $1, $3}'
更好的选择可能是完全忘记grepping/etc/passwd,使用
id
命令获取用户id:

id=$(id -u "${user}" 2>/dev/null) && printf "%s %d\n" "${user}" "${id}"
如果id命令失败,则不会打印任何内容,或者可以将其修改为:

id=$(id -u "${user}" 2>/dev/null)
printf "%s %s\n" "${user}" "${id:-(User not found)}"

在gnu linux中,我很确定找到的用户id不存在是不可能的,因为lastlog只会报告现有用户,所以第二个示例可能毫无意义。

想想名为
sh
的用户会发生什么。有多少用户将
grep sh
匹配?可能它们都是,因为每个都在
shell
字段中使用一些shell

你应该想想

awk -F ':' '$1 == "'"$user"'" {print $1, $3}' /etc/passwd
或使用用户的awk变量:

awk -F ':' -vuser="$user" '$1 == user {print $1, $3}' /etc/passwd

想想名为
sh
的用户会发生什么。有多少用户将
grep sh
匹配?可能它们都是,因为每个都在
shell
字段中使用一些shell

你应该想想

awk -F ':' '$1 == "'"$user"'" {print $1, $3}' /etc/passwd
或使用用户的awk变量:

awk -F ':' -vuser="$user" '$1 == user {print $1, $3}' /etc/passwd

grep | awk
确实是一种反模式(记住:awk内置了模式匹配),但这不意味着它会打印出所有与grep不匹配的内容吗?此外,当涉及到打印所有已登录的用户时,该代码也起作用in@Ohunter,
awk'/never/{…}'
grep never | awk'{…}'
相同,但没有额外进程和FIFO.BTW的开销,请参阅。特别是对于搜索
/etc/passwd
,运行
getentpasswd“$user”
更有意义,它也将与其他目录源(LDAP、NIS等)一起工作,但是如果要搜索
/etc/passwd
,那么
awk-v user=“$user”-F:'$1==user{print$3}
将只搜索精确的匹配项(不是子字符串),并且只在您实际要搜索的一个字段中。
grep | awk
是反模式(请记住:awk内置了模式匹配)当然,但这不意味着它会打印出所有与grep不匹配的内容吗?而且,当涉及到打印所有已登录的用户时,代码也会起作用in@Ohunter,
awk'/never/{…}'
的作用与
grep never | awk'{…}的作用相同“
,但是没有额外的进程和FIFO.BTW的开销,请参阅。特别是对于搜索
/etc/passwd
,运行
getent passwd“$user”
,它也可以与其他目录源(LDAP、NIS等)一起工作,但是如果要搜索
/etc/passwd
,那么
awk-v user=“$user”-F:'$1==user{print$3}'
将只搜索精确匹配项(而不是子字符串),并且仅在您实际要搜索的一个字段中。因此,您是在暗示它与/etc/passwd的其他行相匹配并同时打印?但为什么它会打印出重复项的正确UID?您可以从输出中看到,它会显示两次
守护进程1
。您的awk语句始终打印UID和d同一行的用户名,不能交叉引用,所以可以使用某种正则表达式和grep来过滤掉它?@Ohunter是的,这就是
grep“^$user:”
选项可以。嗯,大多数情况下,如果用户名包含任何regex元字符,则会出现问题。您也可以解决这个问题,但此时您正在谈论在工作区之上堆积工作区,这都是因为您使用了错误的工具。只是