【问题标题】:Convert Multi-line output into single line将多行输出转换为单行
【发布时间】:2013-03-03 22:29:45
【问题描述】:

我管理一个有 40 台 ubuntu 机器的计算机实验室,我拼凑了这个命令来查找学生主目录中大于 100M 的文件的总磁盘使用情况:

for i in `cat ./lab-machines.txt ` ; do ssh $i "nohup find /home -size +100M -print0 | du --files0-from=- -ch | tail -1 && hostname && ls /home" ; done > lab-disk-usage.txt

文件“lab-machines.txt”在单独的一行中包含计算机的主机名。该命令从一个配置了无密码登录的服务器运行到 root 用户的实验室机器。文件 lab-disk-usage.txt 中的输出包含每台机器的类似内容(我在括号中插入了 cmets):

69G total    
hostname
student-username (changes)
admin-username (always the same)
lost+found (always the same)

我希望每台机器的输出如下所示:

69G 主机名学生用户名

我对文本过滤不够熟悉,无法及时完成。你能帮忙吗?

【问题讨论】:

    标签: sed awk


    【解决方案1】:

    试试这个:

    awk -vORS=" " 'NR==1{sub("total","")}NR<=3' file
    

    【讨论】:

    • 这似乎错过了多记录文件中的最后一条记录。
    【解决方案2】:

    通过tr命令管道输出

    您可以尝试更简单的解决方案,例如通过 tr 命令管道输出。例如:

    tr -s "\n" ' ' < lab-disk-usage.txt
    

    不过,这假设文件中只有一条记录。如果您计划拥有多条记录,则需要先通过 tr 管道过滤每条记录,然后再将其附加到输出文件。例如:

    your_pipeline_commands | tr -s "\n" ' ' > lab-disk-usage.txt
    

    使用 Perl 的触发器操作符

    如果您有一组多行记录,则需要更加聪明。 Perl 在处理多行记录方面提供了一些优于 AWK 的优势,包括触发器运算符。例如:

    perl -ne 'if ( /total/../^lost/ ) {
                  chomp $_; print $_ . " "
              } else {
                  print "\n"
              };
              END { print "\n" };' lab-disk-usage.txt
    

    根据您的实际语料库,您可能需要稍微调整正则表达式以使事情正常运行,但在我的系统上它会做正确的事情。

    用于测试 Perl 的语料库

    69G total
    hostname
    student-username
    admin-username
    lost+found
    
    69G total
    hostname
    student-username
    admin-username
    lost+found
    

    Perl 的示例输出

    69G total     hostname student-username admin-username lost+found 
    69G total     hostname student-username admin-username lost+found 
    

    【讨论】:

    • 顺便说一句,“触发器运算符”有一个 awk 替代方案,即模式范围 (/total/,/^lost/{ })。
    • @Scrutinizer 感谢您指出GNU awk's pattern ranges。当您可以依靠空行作为记录分隔符时,Awk 肯定会更优雅,例如awk -v RS='' '/total/,/^lost/ { gsub(/\n/, " ", $0); print }' lab-disk-usage.txt,虽然 Perl 的 -00 段落模式执行类似的功能。
    【解决方案3】:

    我稍微修改了您的示例数据:

    69G total    
    host1
    jane
    admin-username
    lost+found
    65G total    
    host2
    albert
    admin-username
    lost+found
    

    这可以变成一个表格:

    [ghoti@pc ~/tmp]$ awk 'NR%5==1{size=$1} NR%5==2{host=$1} NR%5==3{user=$1; printf("%-8s%-16s%s\n", size, host, user)}' lab-disk-usage.txt
    69G     host1           jane
    65G     host2           albert
    

    她最重要的是,我们使用模运算符 (NR%5) 来确定我们在每组五行中的位置。

    如果您不能依赖每组五行代码,请说明您的输入数据的结构。还有其他方法可以检测记录边界,例如查找/[0-9]+G total$/,如果NR%5 无法使用:

    [ghoti@pc ~/tmp]$ awk '/G total$/{size=$1; getline host; getline user; printf("%-8s%-16s%s\n", size, host, user)}' lab-disk-usage.txt 
    69G     host1           jane
    65G     host2           albert
    

    这基本上只是 potong 的 GNU sed 建议的 awk 版本,它也可以移植(即不仅仅是 GNU sed):

    [ghoti@pc ~/tmp]$ sed -ne '/G total/{s/ .*//;N;N;s/\n/  /g;p;}' lab-disk-usage.txt 
    69G  host1  jane
    65G  host2  albert
    

    【讨论】:

      【解决方案4】:

      这可能对你有用(GNU sed):

      sed -nr '/ total/{N;N;s/( total\s*)?\n/ /gp}' file
      

      【讨论】:

        【解决方案5】:

        如果记录之间没有空行,你可以先引入一个:

        awk '/total/{print x}1' | awk '{print $1,$3,$4}' RS= OFS='\t' 
        

        有文件内容:

        69G total    
        host1
        jane
        admin-username
        lost+found
        65G total    
        host2
        albert
        admin-username
        lost+found
        

        这会产生:

        69G host1   jane
        65G host2   albert
        

        如果记录之间已经有一个空行,您可以跳过管道之前的部分并使用:

        awk '{print $1,$3,$4}' RS= OFS='\t' file
        

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 2021-11-21
          • 1970-01-01
          • 1970-01-01
          • 2018-04-15
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多