我正在尝试通过在 bash 中使用以下命令提取目录my-repo/text/*/<file_name>.txt中的 txt 文件名
echo "my-repo/text/level1_dir/der.txt my-repo/scripts/af.py my-repo/text/level1_dir/another/er_abc.txt my-repo/text/deep/nested/my-file.txt" | sed -E 's|my-repo/text/.*/([^/]+)\.txt|\1|g'

但我得到以下输出:my-file,而我需要的输出是der er_abc my-file

有办法吗?

2

  • 2
    在第一句中,您说您想从该目录中获取文件名。在下一个命令中,文件是echo命令的字符串。我没有看到任何实际访问目录的命令。此外,当您描述所需的输出时,从您的写作中不清楚您是否需要一个名称数组,还是一个包含所有名称的单个字符串。如果您需要一个字符串,那么如何在字符串内分隔名称?


    – 


  • 1
    您的文件或目录名称可以包含空格吗?


    – 


8 个解决方案
8

你可以使用这个tr | sed解决方案:

echo "my-repo/text/level1_dir/der.txt my-repo/scripts/af.py my-repo/text/level1_dir/another/er_abc.txt my-repo/text/deep/nested/my-file.txt" |
tr ' ' '\n' |
sed -E '/\.txt$/!d; s|my-repo/text/[^ ]+/([^/.]+)\.txt|\1|g'

der
er_abc
my-file

如果 GNUawk是一个选项,则将记录分隔符设置为空格,匹配扩展上的一行,并打印它之前的所有内容,而不是路径分隔符:

echo … | awk -v RS='\\s' 'match($0, /([^/]+)\.txt$/, a) {print a[1]}'

或者,也将字段分隔符设置为点或斜线,检查最后一个字段并打印倒数第二个字段(假设文件名中没有出现其他点):

echo … | awk -v RS='[[:space:]]' -F '[/.]' '$NF=="txt" {print $(NF-1)}'

输出:

der
er_abc
my-file

使用这个 Perl 单行命令:

echo "my-repo/text/level1_dir/der.txt my-repo/scripts/af.py my-repo/text/level1_dir/another/er_abc.txt my-repo/text/deep/nested/my-file.txt" | perl -lane 'print join " ", map { m{([^/]+)[.]txt$} } @F;'

Perl 单行命令使用下列命令行标志::
-e告诉 Perl 在行内查找代码,而不是在文件中。
-n:一次循环输入一行,$_默认将其分配给。
-l:在行内执行代码之前删除输入行分隔符("\n"默认在 *NIX 上),并在打印时附加它。
-a在空格或选项中指定的正则$_表达式上拆分成数组@F-F

map { m{([^/]+)[.]txt$} } @F:对于数组的每个元素@F(= 对于每个路径),返回与正则表达式匹配的捕获字符串(如果有)([^/]+)[.]txt$。:
([^/]+)[.]txt$捕获文件的基本名称.txt
([^/]+):除“/”(斜杠)之外的任何字符出现 1 次或多次,捕获(使用括号)并返回。
[.]:文字点。
txt$txt字符串后跟行尾。

参见:

find如果从 而不是开始echo,那么任务就更容易了:

find my-repo/text/ -type f -name '*.txt' -exec basename {} '.txt' \;

.txt这将在目录树中查找带有扩展名的文件my-repo/text/,并打印它们的基本文件名,删除目录名和扩展名.txt

使用find内置printf语句,您可以轻松实现您的目标

$ find /path/to/search/dir -type f -name '*.txt' -printf '%f\n'

或者如果你想使用 NULL 分隔符

$ find /path/to/search/dir -type f -name '*.txt' -printf '%f\0'

一种bash方法是使用 globstarnullglob

#!/usr/bin/env bash

shopt -s globstar nullglob

files=(./my-repo/**/*.txt)

basename -s '.txt' "${files[@]}"

使用mapfile

shopt -s globstar nullglob

mapfile -t  files < <(basename -s '.txt' ./my-repo/**/*.txt)

如果没有basenamebash 内部的替代方法是使用

files=("${files[@]##*/}")
files=("${files[@]%.txt}")

printf '%s\n' "${files[@]}"

这可能对你有用(GNU sed):

sed -E 's/ *my-repo\S*\/(\S+)\.txt|\S+/\1\n/;/^\S/P;D' file

如果行首以可选空格开头,后跟my-repo,后跟零个或多个非空格字符,后跟一个正斜杠,后跟一个或多个非空格字符,后跟.txt,则将其替换为最后一个正斜杠与字符串.txt和换行符之间的字符串。否则,只需将该字符串替换为换行符。

如果现在行首包含一个无空格字符,则打印直至换行符为止。

删除直至换行符并重新执行。

注意:该/^regexp/P;D模式会逐渐减少包含换行符的行,打印或不打印直到第一个换行符,直到该行完全被消耗掉。

要提取您描述的文件名,可以使用以下 bash 命令:

echo "my-repo/text/level1_dir/der.txt my-repo/scripts/af.py my-repo/text/level1_dir/another/er_abc.txt my-repo/text/deep/nested/my-file.txt" | grep -oP 'my-repo/text/.*?/\K[^/]+(?=\.txt)'