1

首先,我不是程序员——只是想学习 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——但我做错了:(

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

我正在尝试获取用户选择的文件名,然后将其用于版本号(始终x.xx.x 在文件名中的某个位置),并最终创建一个目录。

有人可以给我一些指示命令链应该是什么样子吗?我对函数的结构非常不确定,所以任何帮助表示赞赏。

编辑:

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

Pastebin(长代码。)

4

1 回答 1

3

我有消息要告诉你。您正在编写 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 是。

于 2013-03-16T11:30:10.580 回答