我有大量文件夹,每个文件夹都包含一个文件名genome.txt
,我需要将它们cp
放在同一个文件夹中。我正在尝试弄清楚如何做到这一点,以便文件看起来像genome1.txt
、genome2.txt
等等。我正在寻找一个简单的解决方案。
答案1
Shell脚本及其使用
这可以通过一个利用find ...|while read var ; do ... done
结构(在处理文件名时非常常见)的简单 shell 脚本来完成。下面的 bash 脚本对当前(最顶层)目录进行操作,并接受单个参数作为目标。
#!/bin/bash
find -name "genome.txt" -print0 | while IFS= read -r -d '' path
do
base=$(basename --suffix=".txt" "$path")
new_path="${1%/}"/"$base"$counter".txt"
echo "$path" "$new_path"
counter=$(( $counter +1 ))
done
>>>注意<<<:脚本echo
仅用于测试目的。当您对结果路径满意时,将其替换echo
为mv
移动所有文件名或cp
复制所有文件名。
例子:
bash-4.3$ tree
.
├── destination
├── dir1
│ └── genome.txt
├── dir2
│ └── genome.txt
├── dir3
│ └── genome.txt
└── move_enumerated.sh
4 directories, 4 files
bash-4.3$ ./move_enumerated.sh ./destination
./dir2/genome.txt ./destination/genome.txt
./dir3/genome.txt ./destination/genome1.txt
./dir1/genome.txt ./destination/genome2.txt
改进脚本以获得更大的灵活性
该脚本可以进一步改进,使其更加通用,用户可以将文件名、要遍历的顶级目录和目标全部指定为命令行参数:
#!/bin/bash
find "$2" -name "$1" -print0 | while IFS= read -r -d '' path
do
base=$(basename --suffix=".txt" "$path")
new_path="${3%/}"/"$base"$counter".txt"
echo "$path" "$new_path"
counter=$(( $counter +1 ))
done
测试运行:
bash-4.3$ ./move_enumerated.sh "genome.txt" "./testdir" "./testdir/destination"
./testdir/dir2/genome.txt ./testdir/destination/genome.txt
./testdir/dir3/genome.txt ./testdir/destination/genome1.txt
./testdir/dir1/genome.txt ./testdir/destination/genome2.txt
语法和操作理论
总体而言,脚本利用了command | while read variable ; do ... done
结构。这是一种非常常见的方法,它经常用于避免处理ls
可能破坏脚本的复杂文件名。
在管道的左侧,我们有find
命令,它将目录作为参数(如果没有给出,find
Linux 中使用的 GNU 会假定为.
- 当前工作目录)。其他选项包括-name
我们正在搜索的特定文件名,以及-print0
用于以不可打印\0
字符分隔的方式输出结果的选项。它经常用于避免在换行符或其他字符上进行拆分,因为这些字符可能会出现在文件名本身中,从而破坏脚本。
在管道的右侧,我们有while IFS= read -r -d '' ; do . . . done
结构。内置 shell 的while
循环read
经常用于实际stdin
输入,在本例中来自管道。IFS=
-r
和-d ''
是必要的,以确保我们安全地接收文件名,并识别每个项目都以 分隔\0
。
脚本的其余部分相当简单。我们使用basename
命令提取文件的基本名称。由于在这种情况下,我们专门处理已知扩展名并希望文件名中有一个点,因此我们可以使用--suffix=".txt"
去掉该部分,留下genome
部分。然后,我们通过连接目标、基本名称和计数器变量来构建文件的新路径。请注意,"${3%/}"
在改进的脚本中,我们使用参数扩展和参数(目标文件夹)。这样做是为了确保无论用户是否/
在命令行(./destination
或./destination/
)中添加字符,我们都只提取裸目录名,并通过不同的方式将其/
与基本名称连接起来。还要注意,counter
变量最初没有设置,所以我们收到的第一个文件名将是纯文本。genome.txt
之后,计数器变量将递增并因此被创建,并将在我们处理其他文件名时显示出来。
欲了解更多信息,请阅读Shell 中的文件名和路径名:如何正确使用。