Shell脚本包含Perl一行脚本,但结果为空白。

3

我有一个Perl的一行代码,在命令行上可以很好地工作:

perl -nle 'm"\w+:x:\d+:\d+:\S+:/S+:(\S+)$" and $h{$1}++; END{ print "$_: $h{$_}" foreach sort { $h{$b} <=> $h{$a} } keys %h }' /etc/textfile

我已经将这段代码放入一个名为shell.sh的shell文件中,这样下一个人就不必复制/粘贴了,只需运行它即可。
#!/bin/sh
perl -nle 'm"\w+:x:\d+:\d+:\S+:/S+:(\S+)$" and $h{$1}++; END{ print "$_: $h{$_}" foreach sort { $h{$b} <=> $h{$a} } keys %h }' /etc/textfile

我在命令行上运行此命令,但没有结果;它只是加载一个新的提示符而没有输出。有人看出我做错了什么吗?
以下是一些系统规格:
Linux版本2.6.32-220.13.1.el6.x86_64
(gcc版本4.4.6 20110731 (Red Hat 4.4.6-3)(GCC)
GNU bash版本4.1.2(1)-release (x86_64-redhat-linux-gnu)
这里是文本文件中的一小部分内容:
rfink:x:140:140:rat fink:/var/lib/rfink:/sbin/nologin                                 
edible:x:16252:10001:eric idle:/users/eidle/:/bin/bash                                       
tsawyer:x:30855:10001:tom sawyer:/users/tsawyer/:/bin/bash                                
karthur:x:30886:10001:King Arthur:/users/karthur/:/bin/bash                                         
karthur:x:30886:10001:king arthur:/users/karthur/:/bin/bash                                         
jcash:x:30887:10001:john cash:/users/jcash/:/bin/bash                              
hpotter:x:30887:10001:harry potter:/users/hpotter/:/bin/bash                              
triddle:x:30956:10001:tom riddle:/users/triddle/:/bin/bash 

你能展示一下文本文件中的样例吗? - choroba
已添加。我相当确定正则表达式是正确的,因为这个单行命令在命令行中可以工作。似乎是关于shell脚本的某些问题搞砸了它。 - kyoob
你提问中的示例中,/etc/textfile 中的所有尾随空格都是原样的吗? - Greg Bacon
最后一行做得很好。每行都有一个尾随空格(其他所有行上的多个空格发生在复制/粘贴中)。 - kyoob
3
这并不能帮上你的忙,但为什么不将这个 Perl 的单行脚本转换成 Perl 脚本,而不是一个 Bash 脚本呢? - gpojd
那可能是最简单的事情,但这被特别要求为.sh文件。所以就这样吧。 - kyoob
2个回答

3

快速回答

perl -nle 'm"\w+:x:\d+:\d+:[^:]+:\S+:(\S+)\s*$" and $h{$1}++;
  END{ print "$_: $h{$_}" foreach sort { $h{$b} <=> $h{$a} } keys %h }' \
  /etc/textfile

你的正则表达式有三个问题。

  1. 组 ID 后面的字段可能带有空格,所以将该子模式替换为 [^:]+,以匹配一个或多个非冒号字符。
  2. 您在匹配主目录的子模式中使用了错误的斜杠。
  3. 在每行末尾插入 \s* 以允许可选的尾随空白。

输出:

/bin/bash: 7
/sbin/nologin: 1

其他方法

Perl 有一个 awk 模式,可以使用它来处理。

perl -F: -lane '++$sh{$F[-1]};
  END{print "$_: $sh{$_}" for sort { $sh{$b} <=> $sh{$a} } keys %sh}' \
  /etc/textfile

必须删除尾部空格似乎会抵消语法上的好处。
perl -F: -lane '($sh = pop @F) =~ s/\s+$//; ++$sh{$sh};
  END{print "$_: $sh{$_}" for sort { $sh{$b} <=> $sh{$a} } keys %sh}' \
  /etc/textfile

您可以使用管道来获得最佳的结果:
perl -pe 's/[^\S\n]+$//' /etc/textfile |
  perl -F: -lane 'print $F[-1]' |
    sort | uniq -c | sort -nr

输出转置了列,但信息并未改变。
注意在管道的第一个命令中使用正则表达式双重否定技术以除了换行符外删除所有空格。
      7 /bin/bash
      1 /sbin/nologin

作为shell脚本

你的问题要求一个shell脚本,所以——参考daxim的答案——就是这样。
#! /bin/sh

perl -MUser::pwent -le \
  '$_->shell && print $_->shell while $_ = getpwent' |
  sort | uniq -c | sort -nr

请注意,这并不处理名为0的shell的病态情况。
如果您不一定想读取系统/etc/passwd,那么您的脚本将变成:
#! /bin/sh

if [ $# -eq 0 ]; then
  echo Usage: $0 passwd-file .. 1>&2
  exit 1
fi

perl -pe 's/[^\S\n]+$//' "$@" |
  perl -lne 'm|\w+:x:\d+:\d+:[^:]+:\S+:(\S+)$| && print $1' |
    sort | uniq -c | sort -nr

不同的系统使用不同的格式,因此我建议您像上面那样明确您的期望,而不是盲目地打印最后一个字段,无论它是什么。这可能意味着需要处理偶尔的空输出。

不错,这个有效!实际上,我只是简化了那个 [^:]+ 条目,并将冒号之间的整个短语更改为另一个 \S+。仍然不确定为什么我原来的一行代码在命令行中可以给我结果,但在 .sh 文件中却不行。 - kyoob
我很高兴它有帮助。我不明白除非你的用户主目录是/SSSSSSS、/SSSSS和/SS(或者存在复制粘贴错误),否则两者都不可能生成输出。 - Greg Bacon
是的,很可能是湿件出了问题。如果我没记错的话(那已经是上周五的事了,所以可能性不大),我手动将代码键入.sh脚本中,所以很容易就会反转斜杠并破坏整个程序。谢谢! - kyoob

2
避免使用临时正则表达式,当存在专用解析器时。
perl -MUser::pwent=getpwent -e'
    while (my $pwent = getpwent) { $h{ $pwent->shell }++; }
    END { print "$_: $h{$_}\n" for sort { $h{$b} <=> $h{$a} } keys %h }
'

避免使用正则表达式,当更简单的构造,如splitindex/substrunpack可以胜任时。在这里我利用了autosplit的优势。
perl -F: -lane'
    $h{ $F[-1] }++;
    END { print "$_: $h{$_}" for sort { $h{$b} <=> $h{$a} } keys %h }
' /etc/textfile

这使得程序更短,更易读。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接