¿Hay alguna manera de hacer que "mv" falle en silencio?


61

Un comando como mv foo* ~/bar/produce este mensaje en stderr si no hay archivos que coincidan foo*.

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

Sin embargo, en el script que estoy trabajando en ese caso estaría completamente bien, y me gustaría omitir ese mensaje de nuestros registros.

¿Hay alguna buena manera de decir mvque se calle, incluso si no se movió nada?


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

1
Bueno sí. Supongo que tenía algo "más agradable" en mente, como un cambio mv. :) Pero eso servirá, por supuesto.
Jonik

3
He visto que algunas mvimplementaciones admiten una -qopción para silenciarlas, pero eso no es parte de la especificación POSIX para mv. En mvGNU coreutils, por ejemplo, no tiene esa opción.
Thomas Nyman

Bueno, no creo que el problema sea cómo mantener "mv" en silencio, sino cómo hacerlo de manera más adecuada. Comprobando si hay algún archivo / dir foo *, y el usuario tiene permiso de escritura, finalmente ejecute "mv", ¿tal vez?
Shâu Shắc

Respuestas:


47

Estas buscando esto?

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

20
tenga en cuenta que el valor de retorno sigue siendo = 0, por lo tanto, cualquiera set -e(que debe usarse en cada script de shell) fallará. puede agregar a || truepara desactivar la verificación de un solo comando.
reto

10
@reto set -eno debe usarse en cada script de shell, en muchos casos hace que la gestión de errores sea aún más compleja de lo que es sin ella.
Chris Down

1
@ ChrisDown veo tu punto. Suele ser una elección entre dos males. Pero en caso de duda, prefiero una ejecución exitosa fallida, que una falla exitosa. (que no se notará hasta que sea visible para un cliente / usuario). Los guiones de Shell son un gran campo de minas para principiantes, ¡es muy fácil pasar por alto un error y su guión se vuelve loco!
reto

14

En realidad, no creo que silenciar mvsea ​​un buen enfoque (recuerde que también podría informarle sobre otras cosas que podrían ser de interés ... por ejemplo, faltar ~/bar). Desea silenciarlo solo en caso de que su expresión global no devuelva resultados. De hecho prefiero no ejecutarlo en absoluto.

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

No se ve muy atractivo, y funciona solo en bash.

O

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

única excepción se encuentra en bashla nullglobserie. Sin embargo, pagas un precio de 3 veces la repetición del patrón glob.


Problema menor con la segunda forma: no puede mover un archivo llamado foo*cuando es el único en el directorio actual que coincide con el glob foo*. Esto se puede solucionar con una expresión glob que no coincide literalmente, es difícil. Por ej [ '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 mvtiene una buena opción de "destino primero" ( -t) y xargspuede omitir la ejecución de su comando si no hay ninguna entrada ( -r). El uso de -print0y en -0consecuencia se asegura de que no haya un desastre cuando los nombres de archivo contienen espacios y otras cosas "divertidas".


2
Tenga en cuenta que -maxdepth, -print0, -0y -rtambién son extensiones de GNU (aunque algunos de ellos se encuentran en otras implementaciones de hoy en día).
Stéphane Chazelas

1
Poige, muchos de nuestros usuarios no usan Linux. Aquí es una práctica estándar, y muy útil, señalar qué partes de una respuesta no son portátiles. El sitio se llama " Unix y Linux", y muchas personas usan alguna forma de BSD o Unix o AIX o macOS o sistemas integrados. No te lo tomes como algo personal.
terdon

No me estoy tomando nada personalmente. Aunque tengo una opinión que has eliminado como veo ahora. Así que repito: encuentro esos comentarios "tenga en cuenta que es GNU" bastante inútiles. No vale la pena agregarlos, en primer lugar, ya que mi respuesta tiene una clara indicación de que se basa en la versión GNU de mv.
Poige

5

Es importante darse cuenta de que en realidad es el shell lo que lo expande foo*a la lista de nombres de archivos coincidentes, por lo que poco mvpodría hacerse por sí mismo.

El problema aquí es que cuando un globo no coincide, algunos shells como bash(y la mayoría de los otros shells similares a Bourne, ese comportamiento defectuoso en realidad fue introducido por el shell Bourne a fines de los años 70) pasan el patrón textualmente al comando.

Entonces, aquí, cuando foo*no coincide con ningún archivo, en lugar de abortar el comando (como lo hacen los shells anteriores a Bourne y varios shells modernos), el shell pasa un foo*archivo literal mv, por lo que básicamente solicita mvmover el archivo llamado foo*.

Ese archivo no existe. Si lo hiciera, en realidad habría coincidido con el patrón, por lo que mvinforma un error. Si el patrón hubiera sido en su foo[xy]lugar, mvpodría haber movido accidentalmente un archivo llamado en foo[xy]lugar de los archivos fooxy fooy.

Ahora, incluso en esos shells que no tienen ese problema (pre-Bourne, csh, tcsh, fish, zsh, bash -O failglob), aún obtendría un error mv foo* ~/bar, pero esta vez por el shell.

Si desea considerar que no es un error si no hay coincidencia de archivos foo*y, en ese caso, no mover nada, primero querrá crear la lista de archivos (de una manera que no cause un error, como al usar la nullglobopción de algunos shells), y solo la llamada mves si la lista no está vacía.

Eso sería mejor que ocultar todos los errores de mv(como sumar lo 2> /dev/nullharía) como si mvfallara por cualquier otra razón, probablemente aún desee saber por qué.

en zsh

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

O use una función anónima para evitar usar una variable temporal:

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

zshes uno de esos shells que no tienen el error Bourne e informan un error sin ejecutar el comando cuando un glob no coincide (y la nullglobopción no se ha habilitado), por lo que aquí puede ocultar zshel error y restaurar stderr for mvpara que aún vea los mverrores si los hay, pero no el error sobre los globos no coincidentes:

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

O podría usar zargslo que también evitaría problemas si el foo*globo se expandiera a archivos demasiado man.

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

En ksh93:

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

En bash:

bashno tiene sintaxis para habilitar nullglobsolo para un globo, y la failglobopción se cancela, nullglobpor lo que necesitaría cosas como:

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

o configurar las opciones en una subshell para guardarlas, hay que guardarlas antes y restaurarlas después.

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

En yash

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

En fish

En el shell de peces, el comportamiento de nullglob es el predeterminado para el setcomando, por lo que es solo:

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

POSIXY

No hay ninguna nullglobopción en POSIX shy no hay otra matriz que los parámetros posicionales. Hay un truco que puedes usar para detectar si un globo coincide o no:

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

Mediante el uso de a foo[*]y foo*glob, podemos diferenciar entre el caso en el que no hay un archivo coincidente y el caso en el que hay un archivo que se llama foo*(que set -- foo*no se pudo hacer).

Más lectura:


3

Probablemente no sea el mejor, pero puede usar el findcomando para verificar si la carpeta está vacía o no:

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

1
Esto le daría una foo*ruta de búsqueda literal a find.
Kusalananda

3

Supongo que está usando bash, porque este error depende del comportamiento de bash para expandir globos inigualables a sí mismos. (En comparación, zsh genera un error al intentar expandir un globo inigualable).

Entonces, ¿qué pasa con la siguiente solución?

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

Esto ignorará silenciosamente mvsi ls -d foo*falla, mientras que aún registra errores si ls foo*tiene éxito pero mvfalla. (Tenga cuidado, ls foo*podría fallar por otras razones que foo*no existan, por ejemplo, derechos insuficientes, problemas con el FS, etc., por lo que esta solución ignoraría tales condiciones).


Estoy principalmente interesado en bash, sí (y etiqueté la pregunta como bash). +1 por tener en cuenta el hecho de que mvpodría fallar por otras razones que foo*no existen.
Jonik

1
(En la práctica, probablemente no usaré esto, ya que es un poco detallado, y el punto del lscomando no sería muy claro para futuros lectores, al menos sin un comentario.)
Jonik

ls -d foo*podría regresar con un estado de salida distinto de cero por otros motivos, como después de un ln -s /nowhere foobar(al menos con algunas lsimplementaciones).
Stéphane Chazelas

3

Puedes hacer por ejemplo

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

Para más detalles ver: http://mywiki.wooledge.org/BashFAQ/055


mv foo* ~/bar/ 1&>2no silencia el comando, envía lo que habría estado en stdout para estar también en stderr.
Chris Down

y si está usando mingw debajo de Windows (como yo) reemplácelo /dev/nullconNUL
Rian Sanderson

¿Cómo funciona este comando? ¿Qué hacen los símbolos? ¿Qué significa 1y qué 2significa?
Aaron Franke

@AaronFranke 1 es stdout y 2 es stderr pipe por defecto cuando se crea un proceso de Linux. Conozca más sobre tubería en comandos de Linux. Puede comenzar aquí - digitalocean.com/community/tutorials/…
Mithun B

2

Puedes hacer trampa (de forma portátil) con perl:

perl -e 'system "mv foo* ~/bar/" if glob "foo*"'

Por favor comente antes de votar abajo.
Joseph R.

2

Si va a usar Perl, también podría ir hasta el final:

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

o como una línea:

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

(Para obtener detalles del movecomando, consulte la documentación de Archivo :: Copiar ).


-1

En lugar

mv foo* ~/bar/

tu puedes hacer

cp foo* ~/bar/
rm foo* 

Simple, legible :)


66
Copiar, luego eliminar, lleva más tiempo que un simple movimiento. Tampoco funcionará si un archivo es más grande que el espacio libre disponible en el disco.
Anthon

11
El OP quiere una solución que sea silenciosa. Ni cptampoco rmestán en silencio si foo*no existe.
Ron Rothman

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

Si el comando anterior es exitoso o no, podemos encontrar el estado de salida del comando anterior

command: echo $?

si la salida de echo $? es distinto de 0 significa que el comando no es exitoso si la salida es 0 significa que el comando es exitoso

Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.