如何使用 Bash 从文件中的每一行获取基本名称

如何使用 Bash 从文件中的每一行获取基本名称

我有一个包含以下行的文件:

文件1

./filled/Event_repeatFILLED.svelte
./dir2/Miscellaneous_servicesFILLED.svelte
./outline/Line_weightFILLED.svelte
./two-tone/ArchitectureFILLED.svelte
...many more lines ...

我想创建一个仅包含基名的文件:

文件2

Event_repeatFILLED.svelte
Miscellaneous_servicesFILLED.svelte
Line_weightFILLED.svelte
ArchitectureFILLED.svelte
...

目前以下内容不起作用。

sed 's/^.\///' file1 > file2

我如何使用 Shell/Bash 来做到这一点?

答案1

您需要在 sed 替换命令中使用不同的分隔符。例如:

$ sed 's|.*/||' file 
Event_repeatFILLED.svelte
Miscellaneous_servicesFILLED.svelte
Line_weightFILLED.svelte
ArchitectureFILLED.svelte
...many more lines ...

或者:

$ perl -pe 's|.*/||' file 
Event_repeatFILLED.svelte
Miscellaneous_servicesFILLED.svelte
Line_weightFILLED.svelte
ArchitectureFILLED.svelte
...many more lines ...

或者,与awk

$ awk -F'/' '{print $NF}' file 
Event_repeatFILLED.svelte
Miscellaneous_servicesFILLED.svelte
Line_weightFILLED.svelte
ArchitectureFILLED.svelte
...many more lines ...

或者甚至是一些愚蠢的事情,例如:

$ rev file | cut -d / -f 1 | rev
Event_repeatFILLED.svelte
Miscellaneous_servicesFILLED.svelte
Line_weightFILLED.svelte
ArchitectureFILLED.svelte
...many more lines ...

或者您可以按照评论中的建议使用basename,但这会更复杂且更慢:

$ while IFS= read -r fileName; do basename -- "$fileName"; done < file
Event_repeatFILLED.svelte
Miscellaneous_servicesFILLED.svelte
Line_weightFILLED.svelte
ArchitectureFILLED.svelte

最后,如果你真的想要一个纯 shell 解决方案,你可以这样做:

$ while IFS= read -r fileName; do printf '%s\n' "${fileName##*/}"; done < file
Event_repeatFILLED.svelte
Miscellaneous_servicesFILLED.svelte
Line_weightFILLED.svelte
ArchitectureFILLED.svelte
...many more lines ...

但不要使用最后两个,它们效率最低且最复杂。看为什么使用 shell 循环处理文本被认为是不好的做法?

答案2

xargs通过和的 GNU 实现basename,您可以执行以下操作:

xargs -rd '\n' -a file1 basename -a -- > file2

使用的basename优点是可以正确处理一些特殊情况,例如/or /some/dir/

手动执行等效操作:

LC_ALL=C sed -e 's:^//*$:/:;t' -e 's:/*$::; s:.*/::' < file1 > file2
  • LC_ALL=C需要,因为文件路径不能保证由区域设置中的有效文本组成
  • ///, ///... 首先进行特殊处理
  • 尾随/s 已删除
  • 然后将所有内容剥离到最右边/

如果使用zsh,您还可以使用它的:t(for tail) 修饰符(借自csh):

print -rC1 -- ${${(f)"$(<file1)"}:t} > file2

请注意,对于/(or //, ///...) 它与 GNU 的不同之处basename在于它返回空字符串而不是/

perl

perl -MFile::Basename -lpe '$_=basename$_' < file1 > file2

答案3

不要太深入,你的命令的直接问题是点的意思是“任何正则表达式中的“字符”。您需要.*匹配任意数量的字符(在标准正则表达式中尽可能多)。

所以尝试一下

sed 's/^.*\///' file1 > file2

其他答案和评论中提到的注意事项适用。

相关内容