我有消息要告诉你。您正在编写 Bash 脚本,您是程序员!
您的正则表达式 (RE) 属于“错误”类型。Vanillagrep
使用一种称为“基本正则表达式”(BRE) 的形式,但您的 RE 采用扩展正则表达式 (ERE) 的形式。BRE 被 vanilla grep
, vi
,more
等使用。ERE 几乎被其他所有东西使用,awk
, Perl
, Python
, Java
,.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
,因此我们无法预测将使用哪些文件。但是如果我们想对每个文件都这样做呢?
for
在orwhile
循环中使用上述解决方案效率低下。在循环内调用外部进程总是不好的。mkdir
如果不使用 Perl 或 Python 等不同的语言,我们将无能为力。但是sed
,本质上是迭代的,我们应该使用该功能。
一种替代方法是使用shell 模式匹配而不是sed
. 这种特殊模式在 shell 中并非不可能,但会很困难并引发其他问题。所以让我们坚持下去sed
。
我们遇到的一个问题是echo
输出在每个字段之间放置了一个空格。这给我们带来了几个问题。 sed
用换行符“\n”分隔每条记录,所以echo
它自己不会在这里做。我们可以用换行符替换每个空格,但是如果文件名中有空格,那将是一个问题。我们可以对 globbing 做一些诡计IFS
,但这会导致不必要的复杂化。因此,我们将退回到 good old 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/')
这里我使用的是进程替换ls
,这个循环只会调用sed
一次。但是,它调用mkdir
程序n次。
第三课
对不起,但这仍然是低效的。我们为每次迭代创建一个子进程,创建一个目录只需要一个内核 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 已通过此处!但是现在我们有了更多的控制权,并且没有子进程(mkdir
在 Perl 中是内置的,就这样glob
)。
总之,对于少量文件,sed
上面的循环就可以了。它很简单,并且基于 shell。由于 perl 非常大,因此从脚本中为此调用 Perl可能会更慢。但是在循环内创建子进程的 shell 脚本是不可扩展的。Perl 是。