¿Qué hace `mv ./*` sin especificar el destino?

27

Accidentalmente olvidé especificar el destino antes de presionar la tecla Retorno. ¿A dónde mueve mv ./*sin especificar el destino los archivos y directorios en el directorio actual a?

Tim
fuente
44
"Me sorprende que mvacepte 1 argumento" No lo hace. Acepta todos los argumentos que el shell le pasó después de expandir el *.
glglgl

Respuestas:

41

Si el último argumento fue un directorio, simplemente movió todos los archivos y directorios en su directorio de trabajo actual (excepto aquellos cuyos nombres comienzan con puntos) a ese directorio. Si hubiera dos archivos, el primer archivo puede haber sobrescrito el segundo archivo.

Aquí hay algunas demostraciones:

Más de dos archivos y el último argumento es un archivo

$ mkdir d1 d2 d3
$ touch a b c e
$ mv *
mv: target 'e' is not a directory

Más de dos archivos y el último argumento es un directorio

$ mkdir d1 d2 d3
$ touch a b c
$ mv -v *
'a' -> 'd3/a'
'b' -> 'd3/b'
'c' -> 'd3/c'
'd1' -> 'd3/d1'
'd2' -> 'd3/d2'

Dos archivos

$ touch a b
$ mv -v *
'a' -> 'b'

Explicación adicional

El shell expande el glob ( *) en argumentos para mv. El globo generalmente se expande en orden alfabético. mvsiempre ve una lista de archivos y directorios. Nunca ve el globo en sí mismo.

El comando mvadmite dos tipos de movimiento. Uno es mv file ... directory. El otro es mv old-file-name new-file-name(o mv old-file-name directory/new-file-name).

terdon
fuente
30

Primero haré una base de prueba: 5 archivos y una carpeta:

touch file1 file2 file3 file4 file5
mkdir folder

A continuación, ejecutaré un comando de prueba. La -vopción especifica que quiero que se impriman todos los comandos que ejecuta el shell stderr. La -xopción especifica que quiero que se imprima lo mismo stderr, pero quiero que se haga después de evaluar el comando pero antes de que el shell lo ejecute.

sh -cxv 'echo mv *'

SALIDA

echo mv *
+ echo mv file1 file2 file3 file4 file5 folder
mv file1 file2 file3 file4 file5 folder

Entonces verá que el comando que le doy al shell es echo mv *y el comando que ejecuta el shell después de * expandirse es echo mvseguido por todos esos archivos y la carpeta.

Por defecto, el shell expandirá globos como:

sh -cxv 'echo file[1-5]'

SALIDA

echo file[1-5]
+ echo file1 file2 file3 file4 file5
file1 file2 file3 file4 file5

Este es el resultado de la set [+-]ffunción glob:

sh -cxvf 'echo file[1-5]'

SALIDA

echo file[1-5]
+ echo 'file[1-5]'
file[1-5]

Entonces, cuando ejecuta un comando en un shell configurado con opciones predeterminadas como mv *el shell, se expande en la *palabra una lista de argumentos de todos los archivos en el directorio actual ordenados de acuerdo con la configuración regional. Realiza la llamada exec(ve)al sistema mv (esencialmente) con esta lista de argumentos adjunta. Entonces mvobtiene todos los argumentos a medida que el shell los engloba y los ordena. Además stracede ver estos efectos, puede usar la depuración nuevamente como:

sh -s -- mv * <<\SCRIPT
sed -n l /proc/$$/cmdline
echo "$@"
SCRIPT

SALIDA

sh\000-s\000--\000mv\000file1\000file2\000file3\000file4\000file5\000folder\
\000$
mv file1 file2 file3 file4 file5 folder

Y portablemente:

( PS4= IFS=/; set -x mv *; : "/$*/" ) 2>&1

SALIDA

: /mv/file1/file2/file3/file4/file5/folder/

Básicamente, el shell se ejecuta mvcon el contenido del directorio (si no está vacío y no incluye archivos / carpetas con nombres que comienzan con .) como su lista de argumentos. mves POSIX especifica para interpretar su argumento final como un directorio si se invoca con más de dos argumentos - de la misma manera lnes (porque, de hecho, son herramientas muy similares en función subyacente) .

Sin echoembargo, es suficiente :

sh -cxv 'mv *' ; ls

SALIDA

mv *
+ mv file1 file2 file3 file4 file5 folder
folder/

Todos los archivos se movieron al argumento final, porque es una carpeta. ¿Y si no es una carpeta?

sh -cxv 'cd *; mv *'; ls . *

SALIDA

cd *; mv *
+ cd folder
+ mv file1 file2 file3 file4 file5
mv: target ‘file5’ is not a directory

.:
folder/

folder:
file1  file2  file3  file4  file5

Así es como debería comportarse POSIX especifica mv en ese caso:

mv [-if] source_file target_file

mv [-if] source_file... target_dir

En la primera forma de sinopsis, la mvutilidad moverá el archivo nombrado por el operando source_file al destino especificado por el archivo target_file . Esta primera forma de sinopsis se supone cuando el operando final no nombra un directorio existente y no es un enlace simbólico que se refiere a un directorio existente. En este caso, si el archivo de origen nombra un archivo que no es de directorio y el archivo de destino finaliza con un /slashcarácter final , mvse tratará como un error y no se procesarán operandos de archivo de origen .

En la segunda forma de sinopsis, mvmoverá cada archivo nombrado por un operando de archivo de origen a un archivo de destino en el directorio existente nombrado por el operando target_dir , o referenciado si target_dir es un enlace simbólico que se refiere a un directorio existente. La ruta de destino para cada archivo de origen será la concatenación del directorio de destino, un solo /slashcarácter si el destino no terminó en a /slash, y el último componente de nombre de ruta del archivo de origen . Se asume esta segunda forma cuando el operando final nombra un directorio existente.

Entonces, si se *expande a:

  • dos archivos

    • Debe tener solo un archivo, que es el primero renamedal segundo después del segundo unlinked.
  • uno o más archivos seguidos al final por un directorio o un enlace a uno

    • Debe tener solo un directorio o un enlace a uno, que es donde se acaban de mover todos los contenidos anteriores de sus padres.
  • Algo más

    • Debería tener un mensaje de error y un suspiro de alivio satisfactorio.
mikeserv
fuente
1
Sí, usando la vieja shcosita, olvidé depurar con eso, pero con respecto a mi pregunta original, ¿significa que el problema no es el shell mv? Eso es desagradable, es más simple de lo que pensaba originalmente, pero es una verdadera trampa.
user2485710
55
@ user2485710, el punto clave es que en Unix, el shell es responsable de expandir los comodines antes de pasar los argumentos de la línea de comando al comando. En Windows, cada comando tiene que expandir los comodines. Esto permite que los shells de Unix ofrezcan funciones avanzadas de comodines que funcionan con cualquier comando.
cjm
2
@mikeserv dado que esos archivos no eran tan importantes, me apresuré a limpiar y pasar al siguiente paso, pero puedo confirmar las cosas tal como aparecen ahora en mi edición de mi primera publicación / pregunta.
user2485710
66
@mikeserv tl; dr, *¿no es un argumento único? ¿Correcto? :)
Bernhard
1
@Bernhard, en realidad, ese es el único caso que no cubrí, porque podría ser.
mikeserv
18

Primero, el shell se expande ./*a todos los archivos en el directorio actual (excepto los archivos que comienzan con un punto).

  • si no hay o solo un archivo: mvfalla
  • si hay dos archivos: el primero se mueve al segundo (que, por lo tanto, se pierde)
  • si hay más de dos archivos:
    • si el último es un directorio: todos los archivos se mueven a este directorio
    • de lo contrario mvfalla.
jofel
fuente
1
Gracias. ¿Cómo saber qué subdirectorio es "el último"?
Tim
77
Simplemente llame echo ./*para ver qué orden usa su shell (generalmente alfabético).
jofel
Si falla con un archivo, ¿por qué no lo dice?
Noumenon
@Noumenon No entiendo tu comentario. mvdevuelve un mensaje de error si se llama con un solo argumento.
jofel
Tienes razón. El mismo comando de mi historia ahora da missing destination file operand after *filename*aunque ayer no lo hizo.
Noumenon
4

Cuando escribe mv ./*, su shell se expandirá ./*antes de ejecutarse mv.

Algunas cosas a tener en cuenta:

  • Si ./*se expande en menos de 2 argumentos mv, lógicamente producirá un error.
  • ./* generalmente se expandirá a cada archivo (incluido el directorio) presente en el directorio actual y no comenzará con un punto.
  • Puede controlar en qué se ./*expande leyendo la documentación de su shell ( man 7 globes un punto de entrada al tema). Diferentes conchas tendrán diferentes opciones.
rahmu
fuente
1

¿Qué mv *hacer?

Aquí hay una respuesta más corta:

El shell expande el comodín *a una lista de contenidos del directorio. Luego el shell pasa esa lista completa al comando. El comando nunca ve *.

El comando mv file1 file2 ... filen directorymoverá file1 ... filen al directorio.

Ejemplo

Aquí hago un directorio de prueba que contiene tres archivos.

$ mkdir t
$ cd t
$ echo a>a; echo b>b; echo c>c
$ ls
a  b  c

No puede mover varios archivos a un solo archivo

$ mv *
mv: target `c' is not a directory

Agreguemos un subdirectorio

$ mkdir d

Usted puede mover varios archivos en un subdiretory

$ mv *
$ ls
d
$ ls d
a  b  c
RedGrittyBrick
fuente
mv file1 file2 ... filen directoryEs muy poco probable que el comando tenga algo que ver *.
mikeserv
@ Mike: Mi respuesta señala que la última etapa de la expansión de shell transforma efectivamente a esta última en la primera. No saber esto parece ser la raíz de la confusión del OP.
RedGrittyBrick
sí, pero mi punto es que el shell no se expandiría *a file dirmenos que haya una configuración regional a dcontinuación f.
mikeserv
@mikeserv: Existe tal configuración regional, mi ejemplo es un cortar y pegar de Putty que se conecta a un sistema CentOS 5.6 GNU / Linux.
RedGrittyBrick
¿Qué localidad es esa? Tu ejemplo es el a b c dque no lo es file ... dir. Solo comenté en absoluto porque no mencionas el tipo en ningún lado y solo dices que eso se *convierte en file ... dirlo que no sucede.
mikeserv