包含Perl one-liner的Shell脚本有空白结果

时间:2012-05-21 15:11:27

标签: regex linux perl command-line

我有一个在命令行上运行正常的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 

2 个答案:

答案 0 :(得分: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*之前插入$以允许每行上的可选尾部空格。
  4. 输出:

    /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
    

    输出会转换列,但您会获得相同的信息。

    请注意在管道的第一个命令中使用regex double-negative technique删除除换行符之外的所有空格。

          7 /bin/bash
          1 /sbin/nologin

    作为shell脚本

    您的问题要求提供一个shell脚本,以便关闭daxim’s answer - 即

    #! /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
    

    不同的系统使用不同的格式,因此我建议按照上述方法确定您的期望,而不是盲目地打印最后一个字段,无论它是什么。这可能意味着应对偶尔的空输出。

答案 1 :(得分:2)

当存在specialised parser时,请避免使用临时正则表达式。

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这样做时,请避免使用reg-ex。在这里,我利用了autosplit

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

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