有没有办法使“ MV”无声地失败?


61

mv foo* ~/bar/如果没有匹配的文件,类似这样的命令将在stderr中生成此消息foo*

mv: cannot stat `foo*': No such file or directory

但是,在这种情况下,我使用的脚本是完全可以的,我想从日志中忽略该消息。

mv即使什么都没有动静,有什么好办法告诉自己保持安静?


13
mv foo* ~/bar/ 2> /dev/null
Thomas Nyman

1
好吧 我想我脑子里有些“更微妙”的东西,比如换了mv。:)但这当然可以。
Jonik

3
我已经看到一些mv实现支持-q使它们安静的选项,但这不是的POSIX规范的一部分mv。将mv在GNU的coreutils,例如,它具有这样的选择。
Thomas Nyman

好吧,我认为问题不在于如何使“ MV”保持安静,而是如何更适当地做到这一点。检查是否有文件/目录foo *,并且用户具有可写权限,最后执行“ mv”,也许吗?
2013年

Answers:


46

您在找这个吗?

$ mv  file dir/
mv: cannot stat file’: No such file or directory
$ mv  file dir/ 2>/dev/null
# <---- Silent ----->

20
注意,返回值仍然是!= 0,因此任何值set -e每个 shell脚本都应使用)将失败。您可以添加|| true来停用单个命令的检查。
雷托

10
@reto set -e不应在每个shell脚本中使用,在许多情况下,它会使错误管理比没有脚本时更为复杂。
克里斯·

1
@ChrisDown我明白你的意思了。通常是在两种弊端之间做出选择。但是,如果有疑问,我宁愿失败的成功运行,而不是失败的成功。(直到对客户/用户可见,它才被注意到)。Shell脚本对于初学者来说是一个很大的领域,错过一个错误非常容易,您的脚本会乱成一团!
雷托

14

实际上,我不认为静音mv是一种好方法(请记住,它可能还会向您报告其他可能引起关注的事情,例如:missing ~/bar)。您只想在全局表达式不返回结果的情况下将其静音。实际上,根本不执行它。

[ -n "$(shopt -s nullglob; echo foo*)" ] && mv foo* ~/bar/

看起来不太吸引人,只能在中使用bash

要么

[ 'foo*' = "$(echo foo*)" ] || mv foo* ~/bar/

只有您bashnullglobset 在一起。但是,您需要为重复的球形图案支付3倍的价格。


第二种形式的次要问题:foo*当文件是当前目录中唯一与glob匹配的文件时,它无法移动名为的文件foo*。可以使用与字面意义不匹配的glob表达式来解决这个问题,很难。例如[ 'fo[o]*' = "$(echo fo[o]*)" ] || mv fo[o]* ~/bar/
fra san

13

find . -maxdepth 1 -name 'foo*' -type f -print0 | xargs -0r mv -t ~/bar/

— GNU mv具有不错的“目标优先”选项(-t),xargs如果根本没有输入(),可以跳过运行其命令-r。使用-print0and并-0相应地确保文件名包含空格和其他“有趣”内容时不会混乱。


2
需要注意的是-maxdepth-print0-0-r也GNU扩展(尽管他们中的一些在其他实现中发现时下)。
斯特凡·查泽拉斯

1
Poige,我们很多用户不使用Linux。指出答案的哪些部分不是可移植的,这在此处是标准做法,非常有帮助。该站点称为“ Unix&Linux”,许多人使用某种形式的BSD或Unix或AIX或macOS或嵌入式系统。不要亲自去做。
terdon

我个人不带任何东西。不过,我认为您已被移除,正如我现在看到的那样。所以我重复:我发现那些注释“注意它是GNU”是毫无意义的。它们根本不值得添加,首先,因为我的回答明确表明它基于GNU版本的mv
poige

5

重要的是要意识到,实际上是将其扩展foo*到匹配文件名列表的外壳程序,因此几乎没有mv办法自己做。

这里的问题是,当全局不匹配时,某些shell bash(以及大多数其他的Bourne类shell,这些错误行为实际上是Bourne shell在70年代后期引入的)将模式逐字地传递给命令。

因此,在这里,当foo*与任何文件都不匹配时,该外壳将逐字记录foo*文件传递给,而不是中止命令(如pre-Bourne shell和几个现代shell一样)mv,因此基本上要求mv移动名为的文件foo*

该文件不存在。如果确实如此,则它实际上将与模式匹配,因此mv报告错误。如果采用这种模式foo[xy],则mv可能会意外移动了一个名为foo[xy]而不是fooxfooy文件的文件。

现在,即使在那些没有问题的shell中(Bourne之前的版本,csh,tcsh,fish,zsh,bash -O failglob),您仍然会遇到错误mv foo* ~/bar,但这一次是shell造成的。

如果你想如果没有文件匹配考虑它不是一个错误foo*,并在这种情况下,不动任何东西,你想先建立文件列表(在不使用会导致这样的错误方式nullglob的选择一些shell),然后仅调用mv列表是非空的。

这总比隐藏所有错误mv(就像添加2> /dev/null那样)好,好像mv由于任何其他原因而失败一样,您可能仍想知道为什么。

在zsh中

files=(foo*(N)) # where the N glob qualifier activates nullglob for that glob
(($#files == 0)) || mv -- $files ~/bar/

或使用匿名函数来避免使用临时变量:

() { (($# == 0)) || mv -- "$@" ~/bar/; } foo*(N)

zsh是没有Bourne错误并且在glob不匹配(并且nullglob未启用该选项)时不执行命令就报告错误的那些shell中的一个,因此在这里,您可以隐藏zsh的错误并还原stderr for,mv因此您仍然会看到mv错误(如果有),但不会看到有关不匹配的glob的错误:

(mv 2>&3 foo* ~/bar/) 3>&2 2>&-

或者您可以使用zargs它,如果将foo*glob扩展为过多的man文件,也可以避免出现问题。

autoload zargs # best in ~/.zshrc
zargs -r -- foo* -- mv -t ~/bar # here assuming GNU mv for its -t option

在ksh93中:

files=(~(N)foo*)
((${#files[#]} == 0)) || mv -- "${files[@]}" ~/bar/

在bash中:

bash没有nullglob仅启用一个glob的语法,并且该failglob选项会取消,nullglob因此您需要执行以下操作:

saved=$(shopt -p nullglob failglob) || true
shopt -s nullglob
shopt -u failglob
files=(foo*)
((${#files[@]} == 0)) || mv -- "${files[@]}" ~/bar/
eval "$saved"

或在子外壳中设置选项以保存,必须先保存它们,然后再还原。

(
  shopt -s nullglob
  shopt -u failglob
  files=(foo*)
  ((${#files[@]} == 0)) || mv -- "${files[@]}" ~/bar/
)

yash

(
  set -o nullglob
  files=(foo*)
  [ "${#files[@]}" -eq 0 ] || mv -- "${files[@]}" ~/bar/
)

fish

在fish shell中,nullglob行为是该set命令的默认行为,因此仅为:

set files foo*
count $files > /dev/null; and mv -- $files ~/bar/

POSIXly

nullglobPOSIX中没有选项,sh除了位置参数外没有数组。您可以使用一个技巧来检测全局是否匹配:

set -- foo[*] foo*
if [ "$1$2" != 'foo[*]foo*' ]; then
  shift
  mv -- "$@" ~/bar/
fi

通过同时使用a foo[*]foo*glob,我们可以区分没有匹配文件的情况和只有一个文件被恰好被调用的情况foo*(a set -- foo*不能做到)。

更多阅读:


3

可能不是最好的,但是您可以使用find命令来检查文件夹是否为空:

find "foo*" -type f -exec mv {} ~/bar/ \;

1
这将为给出文字foo*搜索路径find
库萨兰达

3

我假设您正在使用bash,因为此错误取决于bash的行为以将不匹配的glob扩展到自身。(通过比较,zsh在尝试扩展不匹配的glob时会引发错误。)

那么,以下解决方法呢?

ls -d foo* >/dev/null 2>&1 && mv foo* ~/bar/

这将默默地忽略mvif ls -d foo*失败,而如果ls foo*成功但mv失败则仍然记录错误。(请注意,ls foo*可能由于foo*不存在的其他原因而失败,例如,权限不足,FS问题等,因此此解决方案将忽略这些情况。)


是的,我对bash最为感兴趣(是的,我确实将此问题标记为bash)。考虑到由于mv其他原因(可能foo*不存在)而可能失败的事实,因此+1 。
Jonik

1
(实际上,我可能不会使用它,因为它有点冗长,而且ls命令的要点对于将来的读者来说也不太清楚,至少没有评论。)
Jonik

ls -d foo*也可能由于其他原因而返回非零退出状态,例如ln -s /nowhere foobar(至少在某些ls实现中)之后。
StéphaneChazelas

3

您可以例如

mv 1>/dev/null 2>&1 foo* ~/bar/ 要么 mv foo* ~/bar/ 1&>2

有关更多详细信息,请参见:http : //mywiki.wooledge.org/BashFAQ/055


mv foo* ~/bar/ 1&>2不会使命令静音,它会发送在stdout上也将在stderr上的内容。
克里斯·唐纳

如果您在Windows(如我)下使用mingw,请替换/dev/nullNUL
Rian Sanderson

该命令如何工作?“&”号做什么?什么12意味着什么呢?
亚伦·弗兰克

创建Linux进程时,默认情况下,@ AaronFranke 1是stdout,2是stderr管道。在Linux命令中了解有关管道的更多信息。你可以从这里开始- digitalocean.com/community/tutorials/...
大额牛乙


2

如果要使用Perl,则不妨一路走:

#!/usr/bin/perl
use strict;
use warnings;
use File::Copy;

my $target = "$ENV{HOME}/bar/";

foreach my $file (<foo*>) {
    move $file, $target  or warn "Error moving $file to $target: $!\n";
}

或单线:

perl -MFile::Copy -E 'move $_, "$ENV{HOME}/bar/" or warn "$_: $!\n" for <foo*>'

(有关move命令的详细信息,请参见File :: Copy的文档。)


-1

代替

mv foo* ~/bar/

你可以做

cp foo* ~/bar/
rm foo* 

简单易读:)


6
复制然后删除所需的时间比简单的操作要长。如果文件大于可用的可用磁盘空间,它也将不起作用。
Anthon

11
OP希望提供一个无声的解决方案。如果不存在,也cp不会rm保持沉默foo*
罗恩·罗斯曼2014年

-1
mv foo* ~/bar/ 2>/dev/null

以上命令是否成功,我们可以通过上一个命令的退出状态来查找

command: echo $?

如果输出echo $?如果不是0,则表示命令失败,如果输出为0,则表示命令成功。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.