如何遍历目录中的文件并更改路径并将后缀添加到文件名


562

我需要编写一个脚本以不同的参数启动程序,但是我是Bash的新手。我开始我的程序:

./MyProgram.exe Data/data1.txt [Logs/data1_Log.txt]

这是我想做的伪代码:

for each filename in /Data do
  for int i = 0, i = 3, i++
    ./MyProgram.exe Data/filename.txt Logs/filename_Log{i}.txt
  end for
end for

因此,我真的很困惑如何从第一个参数创建第二个参数,因此看起来像dataABCD_Log1.txt并启动我的程序。

Answers:


736

首先要注意以下几点:当Data/data1.txt用作参数时,它是否真的应该/Data/data1.txt(以斜杠开头)?另外,外循环应仅扫描.txt文件还是/ Data中的所有文件?这是一个答案,/Data/data1.txt仅假设.txt文件:

#!/bin/bash
for filename in /Data/*.txt; do
    for ((i=0; i<=3; i++)); do
        ./MyProgram.exe "$filename" "Logs/$(basename "$filename" .txt)_Log$i.txt"
    done
done

笔记:

  • /Data/*.txt扩展到/ Data中的文本文件的路径(包括 / Data /部分)
  • $( ... ) 运行一个shell命令并将其输出插入命令行中的该点
  • basename somepath .txt输出somepath的基础部分,从结尾删除.txt(例如/Data/file.txt-> file

如果您需要运行MyProgram Data/file.txt而不是/Data/file.txt,请使用"${filename#/}"删除前导斜线。在另一方面,如果它真的Data不是/Data要扫描,只需使用for filename in Data/*.txt


1
显然basename -s是非标准扩展名-我将编辑答案以使用标准语法。
戈登·戴维森

21
如果没有找到文件/不匹配文件,那么我发现for循环执行块的通配符仍然使用filename =“ /Data/*.txt”输入一次。如何避免这种情况?
奥利弗·皮尔曼

20
@OliverPearmain可以shopt -s nullglob在循环之前使用(在循环shopt -u nullglob之后使用,以避免以后出现问题),或者if [[ ! -e "$filename ]]; then continue; fi在循环开始时添加,因此它将跳过不存在的文件。
戈登·戴维森,2015年

9
当某些文件名称中包含空格时,这将不起作用。
伊萨·哈森

4
@Isa只要所有的双引号都到位,它就应该与空白一起使用。忽略任何双引号,您将遇到空格问题。
Gordon Davisson

344

抱歉,该线程无法正常使用,但是每当您通过遍历遍历文件时,都应避免在这种情况下避免遍历不匹配的情况(这会使循环变量扩展为(不匹配的)遍历模式字符串本身)。

例如:

for filename in Data/*.txt; do
    [ -e "$filename" ] || continue
    # ... rest of the loop body
done

参考:重击陷阱


8
这仍然是及时的警告。我以为我的脚本创建不正确,但是我的文件扩展名是小写而不是大写,没有找到文件,并返回了glob模式。啊。
RufusVS

5
由于使用了Bash标签:这就是shopt nullglob目的!(或shopt failglob也可以使用,取决于您想要的行为)。
gniourf_gniourf

此外,这还在循环到达之前检查在处理期间删除的文件。
loxaxs

1
还可以处理您拥有一个名为dir.txt
vidstige

75
for file in Data/*.txt
do
    for ((i = 0; i < 3; i++))
    do
        name=${file##*/}
        base=${name%.txt}
        ./MyProgram.exe "$file" Logs/"${base}_Log$i.txt"
    done
done

name=${file##*/}取代(壳参数扩展)删除前导路径名直到最后/

所述base=${name%.txt}取代去除拖尾.txt。如果扩展名可以变化,这会有些棘手。


3
我相信您的代码中有错误。一行应该是base=${name%.txt},而不是base=${base%.txt}
caseklim

4
@CaseyKlimkowsky:是的;当代码和注释不一致时,至少其中之一是错误的。在这种情况下,我认为这只是代码。通常,实际上两者都是错误的。感谢您指出了这一点; 我已经解决了。
乔纳森·勒夫勒

8

您可以将finds null分隔的输出选项与read一起使用,以安全地遍历目录结构。

#!/bin/bash
find . -type f -print0 | while IFS= read -r -d $'\0' file; 
  do echo "$file" ;
done

所以对于你的情况

#!/bin/bash
find . -maxdepth 1 -type f  -print0 | while IFS= read -r -d $'\0' file; do
  for ((i=0; i<=3; i++)); do
    ./MyProgram.exe "$file" 'Logs/'"`basename "$file"`""$i"'.txt'
  done
done

另外

#!/bin/bash
while IFS= read -r -d $'\0' file; do
  for ((i=0; i<=3; i++)); do
    ./MyProgram.exe "$file" 'Logs/'"`basename "$file"`""$i"'.txt'
  done
done < <(find . -maxdepth 1 -type f  -print0)

将在脚本(流程)的当前范围内运行while循环,并在需要时允许将find的输出用于设置变量


2
$'\0'是一种奇怪的写作方式''。您丢失IFS=了,-r切换到read:您的已读语句应为:IFS= read -rd '' file
gniourf_gniourf

我认为有些需要搜索$'\0'并在其中散布一些堆栈点。进行您指出的编辑。没有IFS=尝试的不良影响echo -e "ok \nok\0" | while read -d '' line; do echo -e "$line"; done似乎没有。另外,我看到的-r通常是默认设置,但是找不到防止它发生的示例。
詹姆斯,

4
IFS=如果文件名以空格结尾,则需要使用:try with with touch 'Prospero '(注意尾随空格)。另外-r,如果文件名带有反斜杠,则还需要进行切换:尝试使用touch 'Prospero\n'
gniourf_gniourf

-1

看起来您正在尝试执行Windows文件(.exe),当然应该使用powershell。无论如何,在Linux bash shell上,一个简单的单行代码就足够了。

[/home/$] for filename in /Data/*.txt; do for i in {0..3}; do ./MyProgam.exe  Data/filenameLogs/$filename_log$i.txt; done done

或一口气

#!/bin/bash

for filename in /Data/*.txt; 
   do
     for i in {0..3}; 
       do ./MyProgam.exe Data/filename.txt Logs/$filename_log$i.txt; 
     done 
 done
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.