【问题标题】:Create directory based on part of filename根据部分文件名创建目录
【发布时间】:2013-03-16 10:56:42
【问题描述】:

首先,我不是程序员 - 只是想学习 shell 脚本的基础知识并尝试一些东西。

我正在尝试为我的 bash 脚本创建一个函数,该函数根据用户在列表中选择的文件的文件名中的版本号创建目录。

函数如下:

lav_mappe () {

shopt -s failglob
echo "[--- Choose zip file, or x to exit ---]"
echo ""
echo ""

select zip in $SRC/*.zip
do 
[[ $REPLY == x ]] && . $HJEM/build
[[ -z $zip ]] && echo "Invalid choice" && continue
echo
    grep ^[0-9]{1}\.[0-9]{1,2}\.[0-9]{1,2}$ $zip; mkdir -p $MODS/out/${ver}
done
}

我也尝试过使用其他一些命令:

for ver in $zip; do
grep "^[0-9]{1}\.[0-9]{1,2}\.[0-9]{1,2}$" $zip; mkdir -p $MODS/out/${ver}
done

还有find | grep——但我做错了:(

但它最终对我的正则表达式模式说“不匹配”。

我正在尝试获取用户选择的文件名,然后用 grep 查找版本号(文件名中的某处总是x.xx.x),最后创建一个目录。

有人能给我一些指示,命令链应该是什么样子吗?我对函数的结构非常不确定,因此不胜感激。

编辑:

好的,这就是现在完整功能的样子:(请注意,除了目录创建之外的sed(1)命令不是我创建的,只是在我的代码中实现的。)

Pastebin (Long code.)

【问题讨论】:

    标签: regex bash function shell grep


    【解决方案1】:

    我有消息要告诉你。您正在编写 Bash 脚本,您程序员!

    您的正则表达式 (RE) 属于“错误”类型。 Vanilla grep 使用一种称为“基本正则表达式”(BRE) 的形式,但您的 RE 采用扩展正则表达式 (ERE) 的形式。 BRE 被 vanilla grepvimore 等使用。ERE 几乎被其他所有东西使用,awkPerlPythonJava.Net 等。问题是,您试图在文件内容中查找该模式,而不是在文件名中!

    有一个egrep命令,或者你可以使用grep -E,所以:

    echo $zip|grep -E '^[0-9]\.[0-9]{1,2}\.[0-9]{1,2}$'
    

    (注意单引号比双引号更安全)。顺便说一句,您在前面使用^,在末尾使用$,这意味着文件名仅包含版本号,但您说版本号是“文件名中的某个位置”。您不需要 {1} 量词,这是隐含的。

    但是,您似乎也没有捕获版本号。

    您可以使用sed(我们还需要-E):

    ver=$(echo $zip| sed -E 's/.*([0-9]\.[0-9]{1,2}\.[0-9]{1,2}).*/\1/')
    

    右侧的\1 表示“将所有内容(这就是我们在前后都有.* 的原因)替换为括号组中匹配的内容”。 这有点笨拙,我知道。

    现在我们可以使用mkdir(将所有内容都放在一行中没有任何好处,这会使代码更难维护):

    mkdir -p "$MODS/out/$ver"
    

    ${ver} 在这种情况下是不必要的,但最好用双引号将路径名括起来,以防任何组件嵌入了空格。

    所以,对于“非程序员”来说,付出很大的努力,尤其是在生成 RE 方面。

    现在开始第 2 课

    在一般循环中使用此解决方案时要小心。您的问题专门使用select,因此我们无法预测将使用哪些文件。但是如果我们想对每个文件都这样做呢?

    forwhile 循环中使用上述解决方案将效率低下。在循环内调用外部进程总是不好的。如果不使用 Perl 或 Python 等不同的语言,我们无法对 mkdir 做任何事情。但是sed,本质上是迭代的,我们应该使用这个特性。

    另一种选择是使用 shell 模式匹配 而不是 sed。这种特殊模式在 shell 中并非不可能,但会很困难并引发其他问题。所以让我们坚持sed

    我们遇到的一个问题是echo 输出在每个字段之间放置了一个空格。这给我们带来了几个问题。 sed 用换行符“\n”分隔每条记录,所以 echo 本身不会在这里做。我们可以用换行符替换每个空格,但是如果文件名中有空格,那将是一个问题。我们可以用IFS 和通配符做一些诡计,但这会导致不必要的复杂化。因此,我们将转而使用旧的 ls。通常我们不想使用ls,shell globbing 更有效,但这里我们使用的功能是在每个文件名后放置一个换行符(通过管道重定向使用时)。

    while read ver
    do
        mkdir "$ver"
    done < <(ls $SRC/*.zip|sed -E 's/.*([0-9]{1}\.[0-9]{1,2}\.[0-9]{1,2}).*/\1/')
    

    这里我使用进程替换,这个循环只会调用lssed 一次。但是,它调用了mkdir 程序 n 次。

    第 3 课

    很抱歉,但这仍然效率低下。我们为每次迭代创建一个子进程,创建一个目录只需要一个内核 API 调用,但我们只是为此创建一个进程?让我们使用像 Perl 这样更复杂的语言:

    #!/usr/bin/perl
    
    use warnings;
    use strict;
    
    my $SRC = '.';
    
    for my $file (glob("$SRC/*.zip"))
    {
        $file =~ s/.*([0-9]{1}\.[0-9]{1,2}\.[0-9]{1,2}).*/$1/;
        mkdir $file or die "Unable to create $file; $!";
    } 
    

    您可能需要注意,您的 RE 已经通过了这里!但是现在我们有了更多的控制权,并且没有子进程(Perl 中的mkdir 是内置的,glob 也是如此)。

    总之,对于少量文件,上面的sed 循环就可以了。它很简单,并且基于 shell。从脚本中调用 Perl 只是为了这个可能会更慢,因为 perl 相当大。但是在循环内创建子进程的 shell 脚本是不可扩展的。 Perl 是。

    【讨论】:

    • 啊!经过我所有的努力,我发现我错过了最简单的东西,“echo $zip”,哈哈!每天学习!我看到使用 SED 命令,它改变了我对如何在文件中搜索字符串的看法,而不是使用 grep :) 我现在将编写我的函数并完成代码,我将在此处发布以显示结果!
    • @user1170663:重点是grep 在文件中搜索字符串,但这不是您想要做的,您想从文件名中提取一个字符串。不同。
    • @user1170663:查看我的新编辑。恐怕我已经过火了!
    • 酷!这真是很棒的东西!对不起,我会投票赞成,但我没有足够的代表,他:/