Cosa fa `mv ./*` senza specificare la destinazione?


27

Ho dimenticato per sbaglio di specificare la destinazione prima di premere il tasto Invio. Da dove viene mv ./*senza specificare la destinazione spostare i file e le directory nella directory corrente in?



4
"Sono sorpreso che mvaccetta 1 argomento" No. Accetta tutti gli argomenti che la shell le ha passato dopo aver espanso il file *.
glglgl,

Risposte:


41

Se l'ultimo argomento era una directory, hai appena spostato tutti i file e le directory nella directory di lavoro corrente (tranne quelli i cui nomi iniziano con punti) in quella directory. Se c'erano due file, il primo file potrebbe aver sovrascritto il secondo file.

Ecco alcune dimostrazioni:

Più di due file e l'ultimo argomento è un file

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

Più di due file e l'ultimo argomento è una directory

$ 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'

Due file

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

Ulteriori spiegazioni

La shell espande glob ( *) in argomenti per mv. Il glob è generalmente espanso in ordine alfabetico. mvvede sempre un elenco di file e directory. Non vede mai il globo stesso.

Il comando mvsupporta due tipi di spostamento. Uno è mv file ... directory. L'altro è mv old-file-name new-file-name(o mv old-file-name directory/new-file-name).


30

Per prima cosa creerò una base di test: 5 file e una cartella:

touch file1 file2 file3 file4 file5
mkdir folder

Quindi eseguirò un comando di prova. L' -vopzione specifica che desidero che tutti i comandi eseguiti dalla shell vengano stampati stderr. L' -xopzione specifica che voglio che lo stesso sia stampato stderr, ma lo voglio fatto dopo che il comando è stato valutato ma prima che la shell lo esegua.

sh -cxv 'echo mv *'

PRODUZIONE

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

Quindi vedi che il comando I feed the shell è echo mv *e il comando che la shell esegue dopo l' * espansione è echo mvseguito da tutti quei file e la cartella.

Per impostazione predefinita, la shell espanderà globs come:

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

PRODUZIONE

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

Questo è il risultato della set [+-]ffunzione glob:

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

PRODUZIONE

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

Quindi quando si esegue un comando in una shell configurata con opzioni predefinite come mv *la shell si espande nella *parola un elenco di argomenti di tutti i file nella directory corrente ordinati in base alle impostazioni locali. Fa la syscall exec(ve)per mv (essenzialmente) con questa lista di argomenti aggiunta. Quindi mvottiene tutti gli argomenti mentre la shell li stravolge e li ordina. Oltre stracea vedere questi effetti, puoi usare di nuovo il debug come:

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

PRODUZIONE

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

E portabilmente:

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

PRODUZIONE

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

Fondamentalmente, la shell viene eseguita mvcon il contenuto della directory (se non è vuota e non include file / cartelle con nomi che iniziano con .) come elenco di argomenti. mvè POSIX specificato di interpretare il suo argomento finale come una directory se viene invocato con più di due argomenti - allo stesso modo lnè (perché, in effetti, sono strumenti incredibilmente simile in funzione sottostante) .

Basta echoperò:

sh -cxv 'mv *' ; ls

PRODUZIONE

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

Tutti i file sono stati spostati nell'argomento finale, perché è una cartella. E se non fosse una cartella?

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

PRODUZIONE

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

.:
folder/

folder:
file1  file2  file3  file4  file5

Ecco come POSIX specifica mv dovrebbe comportarsi in quel caso:

mv [-if] source_file target_file

mv [-if] source_file... target_dir

Nel primo modulo di sinossi, l' mvutilità deve spostare il file indicato dall'operando source_file nella destinazione specificata dal target_file . Questo primo modulo di sinossi viene assunto quando l'operando finale non nomina una directory esistente e non è un collegamento simbolico che fa riferimento a una directory esistente. In questo caso, se source_file nomina un file non di directory e target_file termina con un /slashcarattere finale , mvlo considererà un errore e nessun operando source_file verrà elaborato.

Nel secondo modulo di sinossi, mvogni file denominato da un operando source_file viene spostato in un file di destinazione nella directory esistente denominata dall'operando target_dir , oppure indicato se target_dir è un collegamento simbolico che fa riferimento a una directory esistente. Il percorso di destinazione per ciascun file_origine deve essere la concatenazione della directory di destinazione, un singolo /slashcarattere se la destinazione non termina in a /slashe l'ultimo componente percorso del file di origine . Questo secondo modulo viene assunto quando l'operando finale nomina una directory esistente.

Quindi se si *espande in:

  • due file

    • Dovresti avere un solo file, che è il primo renamedal secondo dopo il secondo unlinked.
  • uno o più file seguiti per ultimi da una directory o da un collegamento a uno

    • Dovresti avere solo una directory o un link a una, che è dove sono stati spostati tutti i contenuti precedenti dei suoi genitori.
  • qualunque altra cosa

    • Dovresti avere un messaggio di errore e un soddisfacente sospiro di sollievo.

1
sì, usando la vecchia shcosa, mi sono dimenticato di debug con quello, ma per quanto riguarda la mia domanda originale, significa che il problema non è la shell mv? È brutto, è più semplice di quanto pensassi inizialmente ma è una vera trappola.
user2485710

5
@utente2485710, il punto chiave è che in Unix la shell è responsabile dell'espansione dei caratteri jolly prima di passare gli argomenti della riga di comando al comando. In Windows, ogni comando deve espandere i caratteri jolly. Ciò consente alle shell Unix di offrire funzionalità jolly avanzate che funzionano con qualsiasi comando.
cjm

2
@mikeserv dato che quei file non erano così importanti, mi sono affrettato a ripulire e saltare al passaggio successivo, ma posso confermare le cose come appaiono ora nella mia modifica del mio primo post / domanda.
user2485710

6
@mikeserv tl; dr, *non è un singolo argomento? Destra? :)
Bernhard,

1
@ Bernardo - in realtà, questo è l'unico caso che non ho trattato - perché potrebbe essere.
Mikeserv,

18

Innanzitutto la shell si espande ./*in tutti i file nella directory corrente (tranne i file che iniziano con un punto).

  • se non esiste un file o un solo file: mvnon riesce
  • se ci sono due file: il primo viene spostato nel secondo (che quindi si perde)
  • se ci sono più di due file:
    • se l'ultimo è una directory: tutti i file vengono spostati in questa directory
    • altrimenti mvfallisce.

1
Grazie. come dire quale sottodirectory è "l'ultima"?
Tim

7
Chiama per echo ./*vedere quale ordine usa la tua shell (di solito in ordine alfabetico).
jofel,

Se non riesce con un file, perché non lo dice?
Noumenon,

@Noumenon Non capisco il tuo commento. mvrestituisce un messaggio di errore se viene chiamato con un solo argomento.
jofel,

Hai ragione. Lo stesso comando della mia storia ora dà missing destination file operand after *filename*anche se ieri no.
Noumenon,

4

Durante la digitazione mv ./*, la shell si espanderà ./*prima dell'esecuzione mv.

Alcune cose da notare:

  • Se ./*viene espanso in meno di 2 argomenti mv, logicamente produrrà un errore.
  • ./* di solito si espande in ogni file (inclusa la directory) presente nella directory corrente e non inizia con un punto.
  • Puoi controllare ciò che si ./*espande leggendo la documentazione della tua shell ( man 7 globè un punto di accesso all'argomento). Conchiglie diverse avranno opzioni diverse.

1

Cosa fa mv *?

Ecco una risposta più breve:

La shell espande il carattere jolly *in un elenco di contenuti della directory. Quindi la shell passa l'elenco completo al comando. Il comando non vede mai *.

Il comando mv file1 file2 ... filen directorysposta file1 ... filen nella directory.

Esempio

Qui creo una directory di test contenente tre file

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

Non è possibile spostare più file in un singolo file

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

Aggiungiamo una sottodirectory

$ mkdir d

È possibile spostare più file in una sottodirectory

$ mv *
$ ls
d
$ ls d
a  b  c

È mv file1 file2 ... filen directorypoco probabile che il comando abbia qualcosa a che fare con *.
Mikeserv,

@Mike: la mia risposta sottolinea che l'ultimo stadio dell'espansione della shell trasforma effettivamente il secondo nel primo. Non sapere questo sembra essere la radice della confusione del PO.
RedGrittyBrick il

sì, ma il mio punto è che la shell non si espanderebbe *a file dirmeno che non ci siano delle impostazioni locali in dseguito f.
Mikeserv,

@mikeserv: Esistono impostazioni locali di questo tipo, il mio esempio è una copia e incolla di Putty che si collega a un sistema GNU / Linux di CentOS 5.6.
RedGrittyBrick,

che locale è quello? il tuo esempio è quello a b c dche non lo è file ... dir. Ho solo commentato perché non menzioni il tipo da nessuna parte e dici solo che *diventa file ... dirciò che non accade.
Mikeserv,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.