创建以给定格式从文件名中提取的信息的CSV

时间:2014-07-08 12:59:52

标签: perl shell

我有一个小脚本,它列出了目录和所有子目录中所有文件的路径,并使用Perl中的regex解析列表中的每个路径。

#!/bin/sh
find * -type f | while read j; do
echo $j | perl -n -e '/\/(\d{2})\/(\d{2})\/(\d+).*-([a-zA-Z]+)(?:_(\d{1}))?/ && print "\"0\";\"$1$2$3\";\"$4\";\"$5\";$fl\""' >> bss.csv
echo | readlink -f -n "$j" >>bss.csv
echo \">>bss.csv
done

输出:

"0";"13957";"4121113";"2";"/home/root/dir1/bss/164146/13/95/7___/000240216___Abc-4121113_2.jpg"

我正在使用GNU coreutils中的readlink-n在结尾处抑制换行符,-f通过递归跟踪路径上的符号链接来执行规范化。

问题是,当输入字符串没有通过正则表达式时,我只有文件路径行。

如何添加条件以检查正则表达式是否通过 - 显示路径,否则 - 否。 我用各种组合打破了我的大脑,但没有找到任何正常工作。

2 个答案:

答案 0 :(得分:1)

解决方案描述

在Perl中,使用if (/…/) {…} else {…}代替/…/ && …。因此,如果匹配成功,您可以执行打印,否则可以执行其他一些代码。

如果这不是问题而你只想摆脱readlink输出和结束引用,你可以使用反引号从Perl调用readlink

产生的代码

我将所有内容都转换为单个Perl程序,使用File::Find代替find命令,假设$flprint结束时Perl是一个遗留(忽略它)并使用Cwd::realpath()从GNU coreutils中查找文件的规范路径而不是readlink -f。如果您仍想使用readlink -f,请随时将Cwd::realpath($_)更改为`readlink -f '$_'`(包括反引号!),但它对包含单引号的文件名不起作用。

您应该将此脚本称为./script-name starting-directory > bss.csv。如果将它放在您正在检查的目录中,输出也会包含它,以及bss.csv

#!/usr/bin/perl
# Usage: ./$0 [<starting-directory>...]
use strict;
use warnings;
use File::Find;
use Cwd;
no warnings 'File::Find';

sub handleFile() {
    return if not -f;
    if ($File::Find::name =~ /\/(\d{2})\/(\d{2})\/(\d+).*-([a-zA-Z]+)(?:_(\d{1}))?/) {
        local $, = ';', $\ = "\n";
        print map "\"$_\"", 0, $1.$2.$3, $4, $5, Cwd::realpath($_);
    } else {
        print STDERR "File $File::Find::name did not match\n";
    }
}

find(\&handleFile, @ARGV ? @ARGV : '.');

作为参考,我还附上原始程序的抛光版本。它正如我上面建议的那样从Perl调用readlink并且真正使用了Perl的-n选项,避免了while read循环。

#!/bin/sh
find . -type f | perl -n -e 'm{/(\d{2})/(\d{2})/(\d+).*-([a-zA-Z]+)(?:_(\d{1}))?} && print qq{"0";"$1$2$3";"$4";"$5";"`readlink -f -n '\''$_'\''`"}' > bss.csv

对原始代码的其他评论

  • echo |之前的readlink什么都不做,应该删除。 Readlink不会读取它的标准输入。
  • Perl $fl末尾的print来自哪里?我认为这是一种遗留物。
  • 使用qq{}之类的通用引号和周到使用分隔符(例如在正则表达式匹配和其他类似报价的运算符中)可以避免引用地狱。 我上面已经使用过这个提示:/…/m{…}"…"qq{…}。谢谢,Slade有关详细信息,请参阅perlop联机帮助页。

答案 1 :(得分:1)

如果我理解你,你想要捕获文件名的以下部分:

/home/root/dir1/bss/164146/13/95/7___/000240216___Abc-4121113_2.jpg
                           ~~ ~~ ~                ~~~ ~~~~~~~ ~
                           1  2  3                4   5       6

但是你的perl正则表达式并没有这样做。让我们分开以便更好地理解。

/\/(\d{2})\/(\d{2})\/(\d+).*-([a-zA-Z]+)(?:_(\d{1}))?/

切成碎片,这将是......

  • \/(\d{2}) - 斜线,然后是两位数字(捕获数字)
  • \/(\d{2}) - 另一个斜杠和两位数字
  • \/(\d) - 还有一个斜杠和任意数字的数字
  • .*- - 直到输入字符串
  • 中的最后一个连字符的任何字符串
  • ([a-zA-Z]+) - 一个或多个字母字符
  • (?:_(\d{1}))? - 无意义的(我认为)构造匹配一个不会被捕获的可选单个数字(因为它在(?:...)内)

如果您单步执行文件名,您会发现此处没有任何内容可以处理最后一个数字字符串。

我会使用更简单的工具来做到这一点。塞德,例如:

[ghoti@pc ~]$ s="/home/root/dir1/bss/164146/13/95/7___/000240216___Abc-4121113_2.jpg"
[ghoti@pc ~]$ echo "$s" | sed -rne 's/.*/"&"/;h;s:.*/([0-9]{2})/([0-9]{2})/([0-9]+)[^[a-zA-Z]]*[^-]+-([0-9]+)(_([0-9]+))?.*:"0";"\1\2\3";"\4";"\6":;G;s/\n/;/;p'
"0";"13957";"4121113";"2";"/home/root/dir1/bss/164146/13/95/7___/000240216___Abc-4121113_2.jpg"
[ghoti@pc ~]$ 

我将分解sed脚本以便于阅读:

  • s/.*/"&"/; - 在文件名周围加上引号。
  • h; - 将文件名存储在Sed的“保留”空间中,以备将来使用...
  • s: - 开始大替代......
    • .*/([0-9]{2})/([0-9]{2})/([0-9]+)[^[a-zA-Z]]*[^-]+-([0-9]+)(_([0-9]+))?.* - 这是我们想要替换的模式。与您在Perl中所做的类似,显然,但使用ERE而不是PCRE。
    • :"0";"\1\2\3";"\4";"\6":; - 替换模式,其中\n被替换为RE的括号内元素。请注意,替换字符串中会跳过\5,因为该子表达式仅用于匹配。
  • G; - 将“保留”空间附加到模式空间
  • s/\n/;/; - 并删除它们之间的换行符。
  • p - 打印结果。

请注意,此解决方案假设所有输入行都与您要查找的模式匹配。如果情况并非如此,那么您可能会得到不可预测的输出,并且应该在脚本中添加一些模式匹配。