如果 ls 行未找到任何匹配项,则退出脚本

如果 ls 行未找到任何匹配项,则退出脚本

我正在编写这个脚本,其中包含以下行:

ls -lrt /dir1/dir2/filename*.txt | tail -1 | awk '{print $9}' | read variable

我想要的是如果没有找到任何匹配项,则退出脚本(不使用 if 语句)。

答案1

这个命令怎么写

此特定任务不需要管道。

在 zsh 中:

a=(/dir1/dir2/filename*.txt(Nom[1]))
if ((! #a)); then
  echo >&2 "No file matches /dir1/dir2/filename*.txt"
  exit 2
fi
variable=$a[1]

或者,如果 glob 不匹配,则自动退出脚本:

set -e
a=(/dir1/dir2/filename*.txt(om[1]))
variable=$a[1]

其他 shell 没有内置功能来查找最新文件。如果您的文件名仅包含除换行符之外的可打印字符,则调用ls是合理的,但不要传递该-l选项,然后解析除名称之外的所有字符,这是无端的复杂和脆弱(特别是空格中断)。另外,获取排序的第一个匹配项-t,这比获取排序的最后一个匹配项要快-tr。直接的方法:

variable=$(ls -td /dir1/dir2/filename*.txt 2>/dev/null | head -n 1)
if [ -z "$variable" ]; then
  echo >&2 "No file matches /dir1/dir2/filename*.txt"
  exit 2
fi

处理管道

如果您想在管道左侧失败时中止脚本(即以非零状态退出或由于信号而退出),在 ksh93 和 bash 中,您可以设置选项pipefail并在管道状态为非零。

set -e -o pipefail
somecommand | filter
echo "somecommand succeeded"

或者

set -o pipefail
if ! somecommand | filter; then
  echo >&2 "somecommand or filter failed"
  exit 2
fi

Zsh 没有pipefail选项,但您可以检索数组中管道每个组件的状态代码pipestatus

somecommand | filter
if ((pipestatus[1])); then
  echo >&2 "somecommand failed"
  exit 2
fi

在其他 shell 中,管道的状态是右侧命令的状态。没有直接的方法来检索左侧命令的状态。看获取通过管道传输到另一个进程的退出状态一些可能的解决方法。

请注意,filter应该读取 的整个输出somecommand,否则您需要处理somecommand死于 a时的情况信号管道

如果您想根据是否somecommand产生任何输出而不是根据其退出状态来采取行动,您可以使用ifneJoey Hess 的 moreutils。请注意,大多数系统默认情况下没有安装这些实用程序。

答案2

{   ls -lrt /dir1/dir2/filename*.txt || 
    kill $$ 
} | tail -1 | 
awk '{print $9}' | 
read variable

只是kill你自己。有时,当我这样做时,贝壳可能会有点抱怨,但我发现如果我这样做,它甚至不会抱怨kill -PIPE $$

或者,如果您希望具体说明返回代码和其余内容:

trap 'echo some error >&2; exit 1' USR1 $and_or_other_signals
{ ls ... || kill -USR1 $$; } |...

关于其余的..也许另一种方式..?

touch ~/"a n\$ew file
in 'my home
directory"
v=$(ls -1rdt ~/* || kill -PIPE $$; echo /)
v=${v%?/*}; v=${v##*/}
printf '<%s>' ~/"$v"

输出

</home/mikeserv/a n$ew file
in 'my home
directory>

这将使您获得任何目录中最后修改的文件的名称,无论它包含什么字符。我使用 来< >表示变量的开头和结尾,并表明它不包含任何尾随空格。并且该echo位是为了确保如果有尾随空白仍然存在。

它肯定会击败管道,并且如果ls返回 0 以外的值(例如当其命令行参数不存在时),它仍然会杀死脚本化 shell。

无需颠倒排序顺序也可以完成相同的操作,但这有点棘手,并且需要更改为目标目录才能轻松完成此操作:

touch ~/"a n\$ew file
in 'my home
directory"
v=$(cd ~; ls -dtm ./* || kill -PIPE $$)
v=${v#*/}; v=${v%%,?./*}
printf '<%s>' ~/"$v"

输出

</home/mikeserv/a n$ew file
in 'my home
directory>

我认为另一种相当便携的方法:

set /[d]ir1/dir2/filename*.txt
[ -z "${1##/\[*}" ] && exit 1
while [ -n "$2" ] 
do  [ "$1" -nt "$2" ] &&
    set "$@" "$1"
shift; done; v=$1
printf %s\\n "$1" "$v"

相关内容