在“命令内部”而不是在 shell 中展开通配符

在“命令内部”而不是在 shell 中展开通配符

(这是一个在更混乱的环境中最简单的工作示例。)

我有一个d包含文件的目录1.txt 2.txt 3.txt 4.wav。我当前的目录在其他地方。

ls d报告1.txt 2.txt 3.txt 4.wav,理应如此。

但我想要的只是1.txt 2.txt 3.txt,所以我努力ls d/*.txt

但这会d/在每个文件名前面加上 a: d/1.txt d/2.txt d/3.txt,因为 shell在看到它*之前扩展了ls它。

为了获得我想要的1.txt 2.txt 3.txt,我可以ls d | grep txt,或者ls d/*.txt | xargs basename -a,甚至产生一个新的外壳:(cd d; ls *.txt)。有没有更好的方法,不那么笨重,更不容易受到陷阱和极端情况的影响,来过滤 的输出ls

答案1

zsh

printf '%s\n' d/*.txt(:t)

:t与 csh 历史修饰符类似,但在 glob 限定符中,可以让您获得尾巴文件名的。

还:

files=(d/*.txt)
printf '%s\n' $files:t

在其他类似 Bourne 的 shell 中,您始终可以执行以下操作:

(cd d && printf '%s\n' *.txt)

请注意,它不会分叉新的 shell,而是创建一个子 shell 环境。在大多数 shell 实现中,子 shell 环境是通过分叉子进程来实现的,但新的 shell 不会在其中执行(它不是新 shell,而是在新进程中分叉的同一个 shell)。另请注意,如果子 shell 中的最后一个命令是外部命令,则大多数 shell(尽管不是bash)不会派生额外的进程,因此运行的进程总数将与没有子 shell 环境时相同。

ksh93不会 fork 子 shell。它通过在退出子 shell 时撤消在子 shell 中完成的修改来实现此目的。因此,那里(哪里printf也是内置的)(cd d && printf '%s\n' *.txt)不会分叉额外的进程。

另请注意,ls列出了文件和内容它作为参数传递的目录。在这里,ls如果只是为了打印 shell 给出的名称,则不需要,但如果您坚持使用ls,则应该传递该-d选项,以便它不会列出目录的内容:

(cd d && ls -d -- *.txt)

答案2

我相信按照您的要求做的最简单的方法是:

$ ( cd d; ls *.txt )
1.txt  2.txt  3.txt

这是在子 shell 内发生的,( ... )因此目录更改不是永久性的,仅对执行这两个命令有效。

更强大的版本是:

$ ( cd d && ls -d -- *.txt )
1.txt  2.txt  3.txt

除非ls目录更改成功,否则不会执行其中的内容,并ls列出目录而不是其内容,并且不将名称以破折号开头的文件作为选项。


如果您不介意更改位置参数($1$2等):
这也相对简单:

$ set d/*.txt
$ for f do printf '%s\n' "${f#d/}"; done
1.txt
2.txt
3.txt

这将在 POSIX shell 中工作:

$ dash -c 'set d/*.txt; for f do printf "%s\n" "${f#d/}"; done'
1.txt
2.txt
3.txt

你可以使用 GNU find 的-printf

$ find d -maxdepth 1 -iname "*.txt" -printf "%f\n" | sort
1.txt
2.txt
3.txt

但这已经变得足够复杂了。抱歉,据我所知,没有更多“简单”的解决方案。

相关内容