Existe uma maneira de fazer "mv" falhar silenciosamente?

61

Um comando como mv foo* ~/bar/produz essa mensagem no stderr se não houver arquivos correspondentes foo*.

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

No entanto, no script em que estou trabalhando nesse caso, ficaria bem e gostaria de omitir essa mensagem de nossos logs.

Existe alguma maneira agradável de dizer mvpara ficar quieto, mesmo que nada tenha sido movido?

Jonik
fonte
13
mv foo* ~/bar/ 2> /dev/null?
Thomas Nyman
11
Bem, sim. Eu acho que tinha algo "melhor" em mente, como uma mudança para mv. :) Mas isso serve, é claro.
Jonik
3
Eu já vi algumas mvimplementações suportarem uma -qopção para silenciá-las, mas isso não faz parte da especificação POSIX mv. O mvcoreutils no GNU, por exemplo, não possui essa opção.
Thomas Nyman
Bem, eu não acho que o problema é como manter "mv" quieto, mas como fazê-lo de maneira mais adequada. Verificando se existe algum arquivo / dir foo * e o usuário tem permissão de gravação, finalmente execute "mv", talvez?
Shâu Shắc

Respostas:

46

Você está procurando por isso?

$ mv  file dir/
mv: cannot stat file’: No such file or directory
$ mv  file dir/ 2>/dev/null
# <---- Silent ----->
mundo inocente
fonte
20
note, retorne o valor ainda! = 0, portanto, qualquer set -e(que deve ser usado em todos os scripts de shell) falhará. você pode adicionar a || truepara desativar a verificação de um único comando.
reto
10
O @reto set -enão deve ser usado em todos os scripts de shell, em muitos casos, torna o gerenciamento de erros ainda mais complexo do que é sem.
Chris Baixo
11
@ ChrisDown Entendo o seu ponto. Geralmente é uma escolha entre dois males. Mas, em caso de dúvida, prefiro uma execução bem-sucedida com êxito do que uma falha bem-sucedida. (que não é notado até ficar visível para um cliente / usuário). Os scripts de shell são um grande campo minado para iniciantes, é tão fácil perder um erro e seu script dá errado!
reto
14

Na verdade, não acho que o silêncio mvé uma boa abordagem (lembre-se de que você também pode denunciar outras coisas que podem ser interessantes ... por exemplo, em falta ~/bar). Você deseja silenciá-lo apenas no caso de sua expressão glob não retornar resultados. De fato, prefiro não executá-lo.

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

Não parece muito atraente e funciona apenas em bash.

OU

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

somente exceto se você estiver bashcom o nullglobconjunto. Você paga um preço de 3x a repetição do padrão glob.

Miroslav Koškár
fonte
Problema menor com o segundo formulário: falha ao mover um arquivo nomeado foo*quando é o único no diretório atual que corresponde ao glob foo*. Isso pode ser contornado com uma expressão glob que não combina literalmente, é difícil. Por exemplo [ '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/

- O GNU mvpossui uma boa opção "destination first" ( -t) e xargspode pular a execução de seu comando se não houver entrada ( -r). Usar -print0e -0correspondentemente garante que não haja confusão quando os nomes de arquivos contiverem espaços e outras coisas "engraçadas".

poige
fonte
2
Note-se que -maxdepth, -print0, -0e -rtambém são extensões GNU (embora alguns deles são encontrados em outras implementações de hoje em dia).
Stéphane Chazelas
11
Poige, muitos de nossos usuários não usam Linux. É prática padrão aqui, e muito útil, apontar quais partes de uma resposta não são portáteis. O site é chamado " Unix & Linux", e muitas pessoas usam algum tipo de BSD ou Unix ou AIX ou macOS ou sistemas embarcados. Não leve para o lado pessoal.
terdon
Não estou levando nada para o lado pessoal. Embora eu tenha uma opinião que você removeu como agora. Então, repito: acho esses comentários "note que é GNU" bem inúteis. Eles não valem nada a pena acrescentar, em primeiro lugar, já que minha resposta tem uma indicação clara de que é baseada na versão GNU do mv.
poige 03/06
5

É importante perceber que, na verdade, é o shell que o expande foo*para a lista de nomes de arquivos correspondentes, para que haja pouco o mvque fazer.

O problema aqui é que, quando um glob não corresponde, algumas conchas como bash(e a maioria das conchas semelhantes a Bourne, esse comportamento de buggy foi realmente introduzido pelo shell Bourne no final dos anos 70) passam o padrão literalmente para o comando.

Portanto, aqui, quando foo*não corresponde a nenhum arquivo, em vez de interromper o comando (como os shells pré-Bourne e vários shells modernos), o shell passa um foo*arquivo literal para mv, basicamente pedindo mvpara mover o arquivo chamado foo*.

Esse arquivo não existe. Se isso acontecesse, ele realmente corresponderia ao padrão, então mvrelata um erro. Se o padrão tivesse sido foo[xy]substituído, mvpoderia mover acidentalmente um arquivo chamado em foo[xy]vez dos arquivos fooxe fooy.

Agora, mesmo nos shells que não têm esse problema (pré-Bourne, csh, tcsh, fish, zsh, bash -O failglob), você ainda receberia um erro mv foo* ~/bar, mas desta vez pelo shell.

Se você quiser considerar que não é um erro, se não houver nenhum arquivo correspondente foo*e, nesse caso, não mover nada, você deve criar a lista de arquivos primeiro (de uma maneira que não cause erro, usando a nullglobopção de algumas conchas) e, em seguida, apenas chamar mvé a lista não está vazia.

Isso seria melhor do que ocultar todos os erros mv(como adicionar 2> /dev/null) como se mvfalhasse por qualquer outro motivo, você provavelmente ainda desejaria saber o motivo.

em zsh

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

Ou use uma função anônima para evitar o uso de uma variável temporária:

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

zshé um daqueles shells que não possuem o bug Bourne e relatam um erro sem executar o comando quando um glob não corresponde (e a nullglobopção não foi ativada), então aqui, você pode ocultar zsho erro e restaurar stderr para mvque você ainda veja os mverros, se houver, mas não o erro sobre os globs não correspondentes:

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

Ou você pode usar o zargsque também evitaria problemas se a foo*glob se expandisse para arquivos demais.

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

No ksh93:

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

Na festança:

bashnão possui sintaxe para ativar nullglobapenas um glob, e a failglobopção é cancelada nullglob, sendo necessário itens como:

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

ou defina as opções em um subshell para salvar, salve-as antes e restaure-as depois.

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

No yash

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

No fish

No shell do peixe, o comportamento nullglob é o padrão para o setcomando, portanto, é apenas:

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

POSIXly

Não há nullglobopção no POSIX she nenhuma matriz além dos parâmetros posicionais. Existe um truque que você pode usar para detectar se um glob corresponde ou não:

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

Usando a foo[*]e foo*glob, podemos diferenciar entre o caso em que não há arquivo correspondente e aquele em que há um arquivo que é chamado foo*(o que set -- foo*não poderia ser feito).

Mais leitura:

Stéphane Chazelas
fonte
3

Provavelmente não é o melhor, mas você pode usar o findcomando para verificar se a pasta está vazia ou não:

find "foo*" -type f -exec mv {} ~/bar/ \;
Matt
fonte
11
Isso daria um foo*caminho de pesquisa literal para find.
Kusalananda
3

Estou assumindo que você está usando o bash, porque esse erro depende do comportamento do bash para expandir globs incomparáveis ​​para si mesmos. (Em comparação, o zsh gera um erro ao tentar expandir um globo incomparável.)

Então, e a seguinte solução alternativa?

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

Isso ignorará silenciosamente o mvif ls -d foo*falhar, enquanto ainda registrará erros se ls foo*for bem-sucedido, mas mvfalhar. (Cuidado, ls foo*pode falhar por outros motivos que foo*não existem, por exemplo, direitos insuficientes, problemas com o FS, etc., para que essas condições sejam silenciosamente ignoradas por esta solução.)

a3nm
fonte
Estou mais interessado em bash, sim (e marquei a pergunta como bash). +1 por considerar o fato de que mvpode falhar por outros motivos que foo*não existem.
Jonik
11
(Na prática, eu provavelmente não vai estar usando este desde que é tipo de detalhado, eo ponto do lscomando não seria muito claro para futuros leitores, pelo menos sem um comentário.)
Jonik
ls -d foo*também pode retornar com um status de saída diferente de zero por outros motivos, como após um ln -s /nowhere foobar(pelo menos com algumas lsimplementações).
Stéphane Chazelas
3

Você pode fazer por exemplo

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

Para mais detalhes, consulte: http://mywiki.wooledge.org/BashFAQ/055

Valentin Bajrami
fonte
mv foo* ~/bar/ 1&>2não silenciar o comando, ele envia o que estaria no stdout para também estar no stderr.
Chris Baixo
e se você estiver usando mingw no Windows (como eu) substituir /dev/nullcomNUL
Rian Sanderson
Como esse comando funciona? O que os e comerciais fazem? O que significa 1e 2significa?
Aaron Franke
O @AaronFranke 1 é stdout e 2 é stderr pipe por padrão quando um processo Linux é criado. Saiba mais sobre o pipe nos comandos do Linux. Você pode começar aqui - digitalocean.com/community/tutorials/…
Mithun B
2

Você pode trapacear (portably) com perl:

perl -e 'system "mv foo* ~/bar/" if glob "foo*"'
Joseph R.
fonte
Por favor, comente antes da votação.
Joseph R.
2

Se você vai usar o Perl, é melhor ir até o fim:

#!/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";
}

ou como uma linha:

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

(Para detalhes do movecomando, consulte a documentação para File :: Copy .)

Ilmari Karonen
fonte
-1

Em vez de

mv foo* ~/bar/

você pode fazer

cp foo* ~/bar/
rm foo* 

Simples, legível :)

Michał Króliczek
fonte
6
Copiar e depois remover leva mais tempo do que uma simples jogada. Também não funcionará se um arquivo for maior que o espaço livre em disco disponível.
Anthon
11
O OP quer uma solução que seja silenciosa. Nem cpnem rmestão em silêncio, se foo*não existe.
Ron rothman #
-1
mv foo* ~/bar/ 2>/dev/null

Se o comando Above é bem-sucedido ou não, podemos encontrar pelo status de saída do comando anterior

command: echo $?

se a saída de eco $? é diferente de 0 significa comando sem êxito se a saída for 0 significa comando com êxito

Praveen Kumar BS
fonte