编写一个 shell 脚本,将当前目录中包含该单词的所有文件移动hello
到名为 的单独文件夹中hello-world
。
当前目录包含文件a b c d e
和f
其中的文件a
,c
并且f
包含单词hello
。
如何在当前目录的文件中搜索单词hello
并将满足条件的文件移动到另一个目录中hello-world
?
答案1
让我们把这个分解成更小的任务。我们需要
hello-world
如果目录不存在则创建目录- 可靠地识别包含文本的文件
hello
- 可靠地将文件移动到新目录。
创建目录是通过 完成的mkdir
。它将始终拒绝创建已存在的目录(从而拒绝覆盖此类目录),但它也有一个标志-p
,如在man mkdir
-p, --parents
no error if existing, make parent directories as needed
不会告诉您尝试创建的目录是否已存在。
因此我们的脚本的第一个命令可能是(首先检查您是否在正确的目录中)。
mkdir -p hello-world
第二步是确定我们想要的文件。在文件中搜索文本最明显的命令是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.
抑制正常输出。但是,虽然我们可以在您的示例中使用它,但我们真的不希望将文件名作为输出,因为命令的输出只是文本,而文件名可能包含各种字符(从简单的空格到奇特的换行符),这将导致 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
该文件with space
被视为两个独立的文件。
为了识别文件以便让 shell 处理它们,我们应该避免解析文件名。我们可以测试每个文件,看看它是否包含文本,如果包含,则移动它。要循环遍历文件,如上所示,我们可以使用for
具有如下语法的 which:
for var in things; do stuff $var; done
在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.
Bash 中的零表示成功。因此,为了完成我们想要的操作,我们可以编写如下代码
if grep -q hello file; then mv file hello-world; fi
(fi
是命令语法的一部分if
。它告诉 shell 您已经完成了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
此脚本不会移动任何文件,因为echo
beforemv
显示了每次循环迭代将运行哪些命令。echo
如果命令看起来正确,则在测试后删除(请注意,您不能总是可靠地使用它echo
进行测试,并且您可能需要引入一些临时引用才能这样做,但它在这里工作正常)。
*
是当前目录中所有非隐藏文件。使用./*
含义相同的更安全,但要确保所有路径都以./
(即当前工作目录的地址)开头,防止以 开头的文件名被解释为选项。我们已通过在命令中添加和来指示选项的结束.
来-
处理这种可能性。是的详细标志。--
grep
mv
-v
mv
我们应该用引号括住变量,以抑制 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
以下是脚本:
#!/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
echo
当您准备真正移动文件时,请不要忘记删除。
PS,可能还有更有效的方法。我的方法只是举个例子。
答案2
这将是按照您的想法使用 grep 和 xargs 的解决方案:
#!/bin/bash
mkdir hello-world
grep -l hello * | xargs mv -t hello-world
答案3
从名为test.sh
#!/bin/sh
IFS=$'\n'
for i in $( grep -l hello --exclude=*.sh --exclude-dir=hello-world * ); do mv $i hello-world/; done;
从命令行:
IFS=$'\n' && for i in $( grep -l hello --exclude-dir=hello-world * ); do mv $i hello-world/; done;
希望它能帮助你...
请考虑创建第一个目录 hello-world。