【问题标题】:perl + add warn massage in perl one liner codeperl + 在 perl 单行代码中添加警告按摩
【发布时间】:2013-04-27 14:35:37
【问题描述】:

我有一个用于重命名文件/目录名称的 perl 行代码

代码:

find /tmp -name "*$NAME_THAT_WE_WANT_TO_CHANGE*" -exec /tmp/rename.pl  's/\Q$ENV{NAME_THAT_WE_WANT_TO_CHANGE}\E/$1$ENV{NEW_NAME}$2/' {} +

我想在我的代码中添加打印消息 (warn) 以打印将要更改的文件/目录

所以我添加了一行:

       && warn "Rename file - [$ENV{$NAME_THAT_WE_WANT_TO_CHANGE}"'

所以最后更新代码将是:

find /tmp -name "*$NAME_THAT_WE_WANT_TO_CHANGE*" -exec /tmp/rename.pl  's/\Q$ENV{NAME_THAT_WE_WANT_TO_CHANGE}\E/$1$ENV{NEW_NAME}$2/ {} + && warn "Rename file - [$ENV{$NAME_THAT_WE_WANT_TO_CHANGE}"'

当我运行 perl one liner 时,我收到以下错误消息:

    find: missing argument to `-exec'

请告知我需要在我的代码中修复/更新什么?

。 . . . .

完整示例(在我添加警告“.........”之前)

cd /tmp
touch orig-name
touch new-name
export NAME_THAT_WE_WANT_TO_CHANGE=orig-name
export NEW_NAME=new-name



find /tmp -name "*$NAME_THAT_WE_WANT_TO_CHANGE*" -exec /tmp/rename.pl  's/\Q$ENV{NAME_THAT_WE_WANT_TO_CHANGE}\E/$1$ENV{NEW_NAME}$2/' {} +

ls | grep new-name
new-name



more /tmp/rename.pl

#!/usr/bin/perl
#
# rename script examples from lwall:
#       rename 's/\.orig$//' *.orig
#       rename 'y/A-Z/a-z/ unless /^Make/' *
#       rename '$_ .= ".bad"' *.f
#       rename 'print "$_: "; s/foo/bar/ if <stdin> =~ /^y/i' *

$op = shift;
for (@ARGV) {
$was = $_;
eval $op;
die $@ if $@;
rename($was,$_) unless $was eq $_;

【问题讨论】:

  • find 的末尾是 + 吗?不应该是\;吗?
  • 这是对 Perl 的严重误用。为什么与mv $NAME_THAT_WE_WANT_TO_CHANGE $NEW_NAME 不同?
  • 我需要使用 \Q 和 \E 以获得文件/目录的完全匹配,而 perl 是使用它的最佳方式(正则表达式),我不知道其他选项,
  • 另一点是我需要对文件/目录执行递归搜索(/tmp 只是示例)
  • 其他例子:find /tmp -name "*$NAME_THAT_WE_WANT_TO_CHANGE*" -exec perl -e '$op=shift;for(@ARGV){$was=$_;eval$op;die $@if$@;rename$was,$_ unless$was eq$_}' 's/(?

标签: linux perl shell ksh


【解决方案1】:
#! /usr/bin/perl
use autodie;
for (@ARGV) {
  rename $_, $_ =~ s/\Q$ENV{ORIGNAME}\E/$ENV{DESTNAME}/r
    and warn "Rename file - [$_]\n";
}

或者如果你在 CentOS 上,并且不能使用例如cPanel 的新 perl:

#! /usr/bin/perl
for (@ARGV) {
  (my $new = $_) =~ s/\Q$ENV{ORIGNAME}\E/$ENV{DESTNAME}/
    and warn "Rename file - [$_]\n";
  rename $_, $new or die "Rename of ``$_'' to ``$new'' failed: $!";
}

或者如果你真的需要任意 Perl:

#! /usr/bin/perl
my $op = shift;
for (@ARGV) {
  my $old = $_;
  eval $op; die $@ if $@;
  next if $old eq $_;
  rename $old, $_ and warn "Rename file - [$old]\n"
}

注意细微差别。

在前两种情况下,适当调整您的find

find /tmp -name "*$ORIGNAME*" -exec /tmp/rename.pl {} +

我的环境变量不那么痛苦并不重要。

顺便说一句,您的错误不是 Perl 错误,而是 非常明显 find 错误。 find 尤其是 find-exec 很危险,哟:让知道自己在做什么的人在运行脚本之前先看看它。

【讨论】:

  • 我认为最好的选择是选项 2(选项 1 带有“autodie”,我的 perl 版本中没有这个模块)
  • @Eytan,这在通常意义上是危险的:它很容易破坏东西并丢失数据。
【解决方案2】:

为什么不在 Perl 中做所有的事情呢?

#! /usr/bin/env perl

use 5.12.0;
use warnings;
use autodie;
use File::Find;
use Getopt::Long;

在这里休息一下……

File::Find 是一个标准的 Perl 模块,它允许您在 Perl 内部进行文件查找。 Getopt::Long 是一个不错的小命令,可以为您解析命令行参数。这两者都是 Perl 标准包的一部分,绝对没有理由不使用它们。

现在,回到我们正在进行的计划。

my ($from, $to, $dir);

my $usage =<<USAGE;
  find_and_rename.pl -from <from> -to <to> -dir <dir>
USAGE

GetOptions {
    'from=s'     => $from,
    'to=s'       => $to,
    'dir=s'      => $dir,
} or die ($usage\n);

if ( not $from or not $to or not $dir ) {
   die ($usage\n);
}

又一次休息...看看Getopt::Long 解析命令行有多好?单个函数 GetOptions 为您完成所有解析。您需要做的就是验证输入。 (而且,GetOptions 甚至可以为您做到这一点!)。查看Perldoc 并查看几乎所有 Perl 安装中包含的各种模块。它们与 Perl 语言一样,就像 poppush 等命令一样,这些模块可以使 Perl 编程变得更加容易。

my @files_found;
find ( sub {
       return unless /$from/;
       @files_found, $File::Find::name;
     }, $dir);

for my $file ( @files_found ) {
   (here be dragons...)
   rename $file, $to_name;
}

这是您的程序的粗略大纲。这一切都取决于您真正想要完成什么,但它确实展示了如何在单个程序中完成所有事情,而不是依赖使用 find,然后将 Perl 脚本嵌入到 find 中。所有这些都不需要额外的工作。

您可能想要添加更多功能(例如告诉您每个文件被重命名为的内容,并且可能包含一个 -dry-run 选项,您可以使用该选项来查看结果的样子而无需重命名任何内容。

我更喜欢使用find 来填写我以后可以操作的文件名列表。没有理由不能将其包含在查找中。我也喜欢将我的子程序直接嵌入到我的find 命令中。然而:

find ( sub {
       return unless /$from/;
       @files_found, $File::Find::name;
     }, $dir);

相当于:

find ( \&wanted, $dir);

sub wanted {
   return unless /$from/;
   @files_found, $File::Find::name;
}

这可能会让你更容易理解。

要使用您的程序,您只需这样做:

$ find_and_replace.pl -dir . -from REPLACE_THIS -to with_this

现在还有特别奖励!

把最好的留到最后。有一个名为find2perl 的命令会将您的Unix Find 命令转换为Perl 脚本。而且,最好的部分:它已经在您的系统上:

$ find2perl find /tmp -name "*$NAME_THAT_WE_WANT_TO_CHANGE*" -exec /tmp/rename.pl \;

#! /usr/bin/perl -w
    eval 'exec /usr/bin/perl -S $0 ${1+"$@"}'
        if 0; #$running_under_some_shell

use strict;
use File::Find ();

# Set the variable $File::Find::dont_use_nlink if you're using AFS,
# since AFS cheats.

# for the convenience of &wanted calls, including -eval statements:
use vars qw/*name *dir *prune/;
*name   = *File::Find::name;
*dir    = *File::Find::dir;
*prune  = *File::Find::prune;

sub wanted;
sub doexec ($@);


use Cwd ();
my $cwd = Cwd::cwd();


# Traverse desired filesystems
File::Find::find({wanted => \&wanted}, 'find', '/tmp');
exit;


sub wanted {
    /^.*.*\z/s &&
    doexec(0, '/tmp/rename.pl');
}


sub doexec ($@) {
    my $ok = shift;
    my @command = @_; # copy so we don't try to s/// aliases to constants
    for my $word (@command)
        { $word =~ s#{}#$name#g }
    if ($ok) {
        my $old = select(STDOUT);
        $| = 1;
        print "@command";
        select($old);
        return 0 unless <STDIN> =~ /^y/;
    }
    chdir $cwd; #sigh
    system @command;
    chdir $File::Find::dir;
    return !$?;
}

由于您的 doexec 只是一个 Perl 脚本,您可以用您的 Perl 代码替换整个子例程。

【讨论】:

    猜你喜欢
    • 2014-10-27
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-06-15
    • 2013-01-09
    • 2012-05-09
    • 1970-01-01
    • 2011-09-20
    相关资源
    最近更新 更多