【问题标题】:Add filename before first occurrence of a character in all lines for all files in a given folder在给定文件夹中的所有文件的所有行中第一次出现字符之前添加文件名
【发布时间】:2017-05-15 11:18:05
【问题描述】:

我有一个充满文件的文件夹,其中的行如下所示:

S149.sh

sox preaching.wav _001 trim 889.11 891.23
sox preaching.wav _002 trim 891.45 893.92
sox preaching.wav _003 trim 1599.95 1606.78

我想在每行中第一次出现 _ 字符之前添加不带扩展名的文件名(S149),这样它最终看起来像这样:

sox preaching.wav S149_001 trim 889.11 891.23
sox preaching.wav S149_002 trim 891.45 893.92
sox preaching.wav S149_003 trim 1599.95 1606.78

我想为给定文件夹中的每个 *.sh 文件自动执行此操作。

如何使用 bash(包括 awk、grep、sed 等)或 python 来实现?任何帮助将不胜感激。

【问题讨论】:

  • 我看不出S149是这里的文件扩展名。
  • @hek2mgl: S149没有扩展名的文件名。
  • @gniourf_gniourf 说出了我的意思。

标签: python bash awk text-processing


【解决方案1】:

一种可能性,使用ed、标准编辑器和循环:

for i in *.sh; do
    printf '%s\n' ",g/_/ s/_/${i%.sh}&/" w q | ed -s -- "$i"
done

参数扩展${i%.sh} 扩展为$i,其中去掉了后缀.sh

ed 命令在i=S149.sh 的情况下为:

,g/_/ s/_/S149&/
w

,g/_/ 标记所有包含下划线的行,s/_/S149&/ 将下划线替换为S149_。然后w 写入文件。

【讨论】:

  • ++ for good'ol ed,我知道一些基本用法,但你能解释一下1),之前g和2)space之后的原因,g/_/ 以及这如何单独控制第一次出现的替换而不是行中的其他 _
  • @Inian: 1. , 表示整个缓冲区;它是1,$ 的同义词; 2.,g/_/后面的空格无关紧要,这里只是为了美观; 3. 只有第一个_ 被替换,因为s 命令没有被尾随g 标记:它not s/_/S149&/g(标记g 将替换所有非重叠出现次数—g 代表 global)。
  • 现在有意义了!干杯!
  • 只是另一个澄清,要在Vim 中测试它,也许如果我输入,g/_/ s/<pat1>/<pat2>/ 从命令界面: 运行时它实际上并没有替换
  • @Inian: vim 使用ex,而不是ed,在ex 中,完整的缓冲区是%,而, 是当前行,@987654358 的同义词@.
【解决方案2】:

sed 版本:

for i in *.sh; do
    sed -i "s/_/${i%.*}_/g" "$i"
done

${i%.*} 扩展为文件名减去就地替换操作使用的扩展名。

【讨论】:

  • @EdMorton:好吧,如果文件名包含&\<digit>,那么脚本在最终执行时也会失败,即使您的答案中有awk(不提空格) 、换行符、引号、美元、一般的反斜杠和其他 shell 元字符)。问题是我们正在将数据转换为代码。不好的是OP的整体设计和策略。
  • @EdMorton:是的,awk 可以工作,但结果无论如何都是无用的。实际上,在这种情况下,shell 循环会更好,因为正确转义所有字符 :D 是微不足道的。
  • 引用将使用printf '%q' 完成。添加单引号是不够的,以防文件名包含单引号。 sox command.
  • 转义如果你这么称呼它(我之前也是这么称呼它的——它在printf的帮助页面中被称为quoting)。为了完全安全,printf '%q' 将是要走的路。如果文件名包含单引号,单引号是不够的。
  • OP 说“我有一个文件夹,里面装满了如下所示的文件:S149.sh”——在这种情况下,sed 解决方案有效。
【解决方案3】:

使用 GNU awk 进行就地编辑:

awk -i inplace 'FNR==1{f=gensub(/\.[^.]+$/,"",1,FILENAME)} {$3=f$3} 1' *.sh

如果您正在考虑改用 shell 循环,请参阅 why-is-using-a-shell-loop-to-process-text-considered-bad-practice

【讨论】:

  • 谁提到考虑使用 shell 循环来处理文本?
  • 这是新手认为合理的常用方法,因此最好向他们提供事实。
  • 我什至不知道他们会怎么做。使用临时文件并在每一行使用sed
  • for f in *.sh; do while read x y z rest; do echo "$x $y ${f%%.*}$z $rest"; done < "$f" > tmp && mv tmp "$f"; done 或与通常的“无需外部工具”声明类似,好像这是一件好事。
  • 顺便说一句,您在这里并没有完全回答 OP 的要求。 OP 希望替换第一次出现的_,而不是每行的第三个字段。
【解决方案4】:

@Ruran- 如果您没有可以在读取 Input_file 时进行 Input_file 编辑的 awk,那么下面的内容可能会对您有所帮助。

 awk '(FILENAME != P && P && Q){close(P);system("mv " Q OFS P)} {Q=P=FILENAME;sub(/\..*/,X,Q);sub(/_/,Q"&");print > Q;} END{system("mv " Q OFS P)}' *.sh

逻辑,后面很简单,它是更改 _(char) 的第一次出现,然后在读取下一个 Input_file 时将新格式化的行保存到一个 tmp 文件中,它将该临时文件重命名为以前的 Input_file。

还有一点我在上面的帖子中没有看到,因为我们正在使用 *.sh 所以假设你有数千个 Input_files 然后代码可能会出错,这是因为将打开太多 Input_files 而我们不关闭文件,所以我也关闭它们,如果这对你有帮助,请告诉我。

以下也是一种非单线形式的解决方案。

awk '(FILENAME != P && P && Q){
                                close(P);
                                system("mv " Q OFS P)
                              }
                              {
                                Q=P=FILENAME;
                                sub(/\..*/,X,Q);
                                sub(/_/,Q"&");
                                print > Q;
                              }
     END                      {
                                system("mv " Q OFS P)
                              }
    ' *.sh

【讨论】:

  • 我的回答中没有提到可能打开文件过多的问题,因为在使用 GNU awk 时不会发生这种情况,因为 gawk 在内部管理同时打开的文件的数量,并且在其他答案中没有提到,因为它们在循环中单独处理每个文件。您的系统命令应该是system("mv -- \047" Q "\047 \047" P "\047"),顺便说一句,以避免在文件名包含空格或通配符时失败,并且不要使用未初始化的变量X 代替空字符串"" - 它只会混淆您的代码并使它更容易出错。
  • 最后 - 重组 Q=P=FILENAME; sub(/\..*/,X,Q); sub(/_/,Q"&"); 只在输入文件第一次打开时发生一次(即在 FILENAME!=PFNR==1 块中)所以你对每个文件执行一次而不是执行它每行输入一次。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2016-05-11
  • 1970-01-01
  • 2014-11-02
  • 1970-01-01
  • 2020-07-02
  • 2015-08-27
相关资源
最近更新 更多