Bash:ls的子shell行为

3
我想知道为什么以下两个命令的输出不同: ls -1 -tF | head -n 1echo $(ls -1 -tF | head -n 1) 我尝试获取最后修改的文件,但在子shell中使用它时,有时会得到多个文件作为结果?
为什么会这样,如何避免?

我执行 ls -tr | tail -n1,这总是有效的。 - MostWanted
1
引用您的子shell,它将等同于可执行文件的 file*,然后在回显之前进行扩展。-F,--classify append indicator (one of */=>@|) to entries - 123
这里是-F的解释:`function L { myvar=$1; h=${myvar:="1"}; echo "最近修改的 ${h} 个文件:"; export L=$(ls -1 -tF|fgrep -v / |head -n ${h}| sed 's/(*|=|@)$//g' ); ls -l $L; }alias ol='L; xdg-open $L' ` - Martin T.
不要忘记阅读有关parsing ls的危险性... - ghoti
@ghoti:感谢提供链接,但是扩展本地登录 shell(bash)的行为是否不好? - Martin T.
这是一个备受争议的问题。:) 一方面,人们倾向于认为建立不良习惯通常是一个坏主意。如果有更好的方法来做某事,就用更好的方法去做。另一方面,你最了解在你的环境中采用这种做法的风险。如果它对你有效,那可能没问题,特别是如果唯一可能受到伤害的人是你自己。 :-) - ghoti
2个回答

6
问题出现在您使用了未加引号的子shell和-F标志,因为ls输出了附加到文件名的shell特殊字符。

-F, --classify
将指示符(*、/、=、>、@、|之一)附加到条目

可执行文件后面会附加 *。

当您运行

echo $(ls -1 -tF | head -n 1)

那么

$(ls -1 -tF | head -n 1)

将返回一个文件名,如果它恰好是可执行文件并且是另一个文件的前缀,那么它将返回两个文件。

例如,如果你有:

test.sh
test.sh.backup

那么它将返回
test.sh*

当其被输出时会被展开为

test.sh test.sh.backup

引用子Shell可以防止此扩展。
echo "$(ls -1 -tF | head -n 1)"

返回值

test.sh*

1
很好的解释...使用花括号代替括号甚至更好 :) 就像这样: { ls -1 -tF | head -n 1; } 不会创建子shell。 - z atef
@zee 如果没有echo,你可以直接运行命令而不需要任何大括号/圆括号。 - 123

0

我刚刚发现了一个错误: 如果你使用 echo $(ls -1 -tF | head -n 1) 文件全局匹配机制可能会导致额外的匹配。

所以 echo "$(ls -1 -tF | head -n 1)" 可以避免这个问题。

因为如果结果是一个可执行文件,它会在结尾包含 *。

我试图将为什么使用 -F放在注释中,但现在我决定在这里放置它:

我在我的 .bashrc 中添加了以下行,以便快速获取最后修改的文件或目录列表:

function L {
  myvar=$1; h=${myvar:="1"};
  echo "last ${h} modified file(s):"; 
  export L=$(ls -1 -tF|fgrep -v / |head -n ${h}| sed 's/\(\*\|=\|@\)$//g' );    
  ls -l $L;
}
function LD {
  myvar=$1;
  h=${myvar:="1"};
  echo "last ${h} modified directories:"; 
  export LD=$(ls -1 -tF|fgrep / |head -n $h | sed 's/\(\*\|=\|@\)$//g'); ls -ld $LD;
}
alias ol='L; xdg-open $L'
alias cdl='LD; cd $LD'

现在我可以使用 L(或 L 5)列出最后修改的文件(最后 5 个)。但不能列出目录。

而且,使用 L; jmacs $L 我可以打开我的编辑器进行编辑。传统上,我使用别名 lt='ls -lrt',但那样我就必须重新输入名称...

现在,在 mkdir ... 之后,我使用 cdl 切换到该目录。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接