car*_*ior 1 command-line bash scripts files
编写一个 shell 脚本,将当前目录中包含该词的所有文件移动hello到一个名为hello-world.
当前目录包含文件a b c d e和f出哪些文件a,c并f包含单词hello。
如何hello在当前目录的文件中搜索该单词并将满足条件的文件移动到另一个目录中hello-world?
让我们把它分解成更小的任务。我们需要
hello-world,如果它不存在hello制作目录是用mkdir. 它总是拒绝创建一个存在的目录(从而拒绝覆盖这样的目录)但它也有一个标志-p,如man mkdir
-p, --parents
no error if existing, make parent directories as needed
Run Code Online (Sandbox Code Playgroud)
不会费心告诉您您尝试创建的目录是否已经存在。
所以我们脚本的第一个命令可能是(首先检查你是否在正确的目录中)。
mkdir -p hello-world
Run Code Online (Sandbox Code Playgroud)
第二步是确定我们想要的文件。在文件中搜索文本最明显的命令是grep. 如果我们只想要包含 的文件的文件名hello,我们可以使用-l标志,根据man grep
-l, --files-with-matches
Suppress normal output; instead print the name of each input file
from which output would normally have been printed. The scanning
will stop on the first match.
Run Code Online (Sandbox Code Playgroud)
抑制正常输出。但是,尽管我们可以在您的示例中摆脱它,但我们真的不希望将文件名作为输出,因为命令的输出只是文本,并且文件名可能包含各种字符(从不起眼的空格到异国情调的字符)换行符),这将导致 shell 看到与您想要执行某些操作的文件的实际名称不同的内容,并以您不想要的方式运行。例如,为您的脚本设置了一个测试目录,如果我添加一个名称中包含空格的文件,看看会发生什么:
$ echo hello > with\ space
$ ls
a b c d e f with space
$ for i in $(grep -l hello *); do echo "$i"; done
a
c
f
with
space
Run Code Online (Sandbox Code Playgroud)
该文件with space被视为两个单独的文件。
为了识别文件以便让 shell 对它们做一些事情,我们应该避免解析文件名。我们可以测试每个文件,查看它是否包含文本,如果包含,则移动它。要循环文件,如上所示,我们可以使用forwhich 具有如下语法:
for var in things; do stuff $var; doneRun Code Online (Sandbox Code Playgroud)
在for循环内(以及其他任何地方)我们可以使用test命令,它有时看起来像[并且也像[[做条件语句,如果你只想检查文件是否为空,或者是特定类型,我建议您使用该test命令。
但是你想找到一些特定的文本,所以我们应该调用grep,但我们真的只想知道hello在文件中查找是否成功,所以我们知道我们必须对文件做一些事情。
要根据命令的成功做出条件语句,我们可以使用if. if通常与使用test/ [/ [[,但你并不需要这些,因为grep还有另一个有用的标志:
-q, --quiet, --silent
Quiet; do not write anything to standard output. Exit immediately
with zero status if any match is found, even if an error was detected.
Run Code Online (Sandbox Code Playgroud)
Bash 中的零意味着成功。所以为了做我们想做的事,我们可以写一些类似的东西
if grep -q hello file; then mv file hello-world; fi
Run Code Online (Sandbox Code Playgroud)
(这fi是if命令语法的一部分。它告诉外壳你已经完成了你的if)
我通常在交互式 shell 中编写 shell 脚本,如果有的话,只在以后将它们脚本化到一个文件中,因为我很懒,而且我大多只编写非常琐碎的脚本。无论如何,您可以通过将命令与;. 如果出现问题,请按向上箭头键编辑最后一个命令...
我们不想为每个文件运行一次该命令。这将破坏编写脚本来完成这项工作并节省我们打字的目的,因此要循环我们可以使用的文件for。为了避免收到错误grep有关的hello-world目录,我们可以添加其他标记来排除它。
这是我执行此工作的测试命令以及我在测试环境中从中获得的输出:
$ mkdir -p hello-world; for file in *; do if grep -q --exclude-dir=hello-world -- hello "$file"; then echo mv -v -- "$file" hello-world; fi; done
mv -v -- a hello-world
mv -v -- c hello-world
mv -v -- f hello-world
mv -v -- with space hello-world
Run Code Online (Sandbox Code Playgroud)
此脚本不移动任何文件,因为echobeforemv显示了循环的每次迭代将运行的命令。echo如果命令看起来正确,则在测试后删除(请注意,您不能总是可靠地echo用于测试,您可能必须引入一些临时引用才能这样做,但它在这里工作正常)。
*是当前目录中的所有非隐藏文件。使用./*这意味着同样的事情更安全,但确保所有路径都以./(当前工作目录的地址.)开头,防止文件名-以选项开头。我们已经通过增加处理这种可能性--,以grep和该mv命令指示选项的结束。-vismv的详细标志。
我们应该引用变量来抑制 shell 扩展。如果我删除周围的引号"$file",我的输出是
mv -v -- a hello-world
mv -v -- c hello-world
mv -v -- f hello-world
grep: with: No such file or directory
grep: space: No such file or directory
Run Code Online (Sandbox Code Playgroud)
这是脚本作为脚本:
#!/bin/bash
mkdir -p hello-world
for file in *; do
if grep -q --exclude-dir=hello-world -- hello "$file"; then
echo mv -v -- "$file" hello-world
fi
done
Run Code Online (Sandbox Code Playgroud)
echo当您准备好真正移动文件时,请不要忘记删除。
PS,很可能有更有效的方法来做到这一点。我的方法只是一个例子。