我有消息要告诉你。您正在编写 Bash 脚本,您是程序员!
您的正则表达式 (RE) 属于“错误”类型。 Vanilla grep 使用一种称为“基本正则表达式”(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 或while 循环中使用上述解决方案将效率低下。在循环内调用外部进程总是不好的。如果不使用 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/')
这里我使用进程替换,这个循环只会调用ls 和sed 一次。但是,它调用了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 是。