O que faz `mv ./*` sem especificar o destino?


27

Esqueci-me acidentalmente de especificar o destino antes de pressionar a tecla Return. Onde, mv ./*sem especificar o destino, move os arquivos e diretórios no diretório atual para?



4
"Estou surpreso que mvaceite 1 argumento" Não. Ele aceita todos os argumentos que o shell passou para ele depois de expandir o arquivo *.
glglgl

Respostas:


41

Se o último argumento foi um diretório, você acabou de mover todos os arquivos e diretórios do seu diretório de trabalho atual (exceto aqueles cujos nomes começam com pontos) para esse diretório. Se houvesse dois arquivos, o primeiro arquivo pode ter substituído o segundo.

Aqui estão algumas demonstrações:

Mais de dois arquivos e o último argumento é um arquivo

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

Mais de dois arquivos e o último argumento é um diretório

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

Dois arquivos

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

Mais explicações

O shell expande o glob ( *) em argumentos para mv. A glob é geralmente expandida em ordem alfabética. mvsempre vê uma lista de arquivos e diretórios. Ele nunca vê a própria bola.

O comando mvsuporta dois tipos de movimento. Um é mv file ... directory. O outro é mv old-file-name new-file-name(ou mv old-file-name directory/new-file-name).


30

First I'll make a test base - 5 files and one folder:

touch file1 file2 file3 file4 file5
mkdir folder

Em seguida, executarei um comando de teste. A -vopção especifica que eu quero que todos os comandos que o shell execute sejam impressos stderr. A -xopção especifica que eu quero o mesmo impresso stderr- mas quero que seja feito depois que o comando for avaliado, mas antes que o shell o execute.

sh -cxv 'echo mv *'

SAÍDA

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

Então você vê que o comando que eu alimento o shell é echo mv *e o comando que o shell executa após a * expansão é echo mvseguido por todos esses arquivos e a pasta.

Por padrão, o shell expandirá globs como:

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

SAÍDA

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

Este é um resultado da set [+-]ffunção glob:

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

SAÍDA

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

Portanto, quando você executa um comando em um shell configurado com opções padrão, como mv *o shell, expande para a *palavra uma lista de argumentos de todos os arquivos no diretório atual, classificados de acordo com a localidade. Ele faz o syscall exec(ve)for mv (essencialmente) com esta lista de argumentos anexada. Então, mvobtém todos os argumentos conforme o shell os observa e os classifica. Além stracede ver esses efeitos, você pode usar a depuração novamente como:

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

SAÍDA

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

E portably:

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

SAÍDA

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

Basicamente, o shell é executado mvcom o conteúdo do diretório (se não estiver vazio e não incluindo arquivos / pastas com nomes começando com .) como sua lista de argumentos. mvé POSIX especificado para interpretar seu argumento final como um diretório se for invocado com mais de dois argumentos - da mesma maneira que lné (porque, de fato, são ferramentas incrivelmente semelhantes na função subjacente) .

Já chega echo:

sh -cxv 'mv *' ; ls

SAÍDA

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

Todos os arquivos foram movidos para o argumento final - porque é uma pasta. Agora, e se não for uma pasta?

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

SAÍDA

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

.:
folder/

folder:
file1  file2  file3  file4  file5

É assim que o POSIX especifica mv deve se comportar nesse caso:

mv [-if] source_file target_file

mv [-if] source_file... target_dir

No primeiro formulário de sinopse, o mvutilitário deve mover o arquivo nomeado pelo operando source_file para o destino especificado pelo target_file . Esse primeiro formulário de sinopse é assumido quando o operando final não nomeia um diretório existente e não é um link simbólico referente a um diretório existente. Nesse caso, se o source_file nomear um arquivo não-diretório e o target_file terminar com um /slashcaractere à direita , mvtratará isso como um erro e nenhum operando do source_file será processado.

No segundo formulário de sinopse, mvmova cada arquivo nomeado por um operando source_file para um arquivo de destino no diretório existente nomeado pelo operando target_dir ou referenciado se target_dir for um link simbólico referente a um diretório existente. O caminho de destino para cada arquivo de origem deve ser a concatenação do diretório de destino, um único /slashcaractere se o destino não terminar em ae/slash o último componente do nome do caminho do arquivo de origem . Este segundo formulário é assumido quando o operando final nomeia um diretório existente.

Portanto, se se *expandir para:

  • dois arquivos

    • Você deve ter apenas um arquivo, que é o primeiro renamedpara o segundo após o segundo unlinked.
  • um ou mais arquivos seguidos por último por um diretório ou um link para um

    • Você deve ter apenas um diretório ou um link para um, que é onde todos os conteúdos anteriores de seus pais acabaram de ser movidos.
  • algo mais

    • Você deve ter uma mensagem de erro e um suspiro de alívio satisfatório.

11
sim, usando o velho sh, esqueci a depuração com isso, mas com relação à minha pergunta original, isso significa que o problema é que o shell não é mv? Isso é desagradável, é mais simples do que eu pensava inicialmente, mas é uma verdadeira armadilha.
user2485710

5
@ user2485710, o ponto principal é que, no Unix, o shell é responsável por expandir curingas antes de passar os argumentos da linha de comando para o comando. No Windows, cada comando precisa expandir os próprios curingas. Isso permite que os shells do Unix ofereçam recursos avançados de curinga que funcionam com qualquer comando.
CJM

2
@mikeserv, dado que esses arquivos não eram tão importantes, corri para a limpeza e pulei para a próxima etapa, mas posso confirmar as coisas como elas aparecem agora na minha edição da minha primeira postagem / pergunta.
user2485710

6
@mikeserv tl; dr, *não é um argumento único? Direita? :)
Bernhard

11
@ Bernhard - na verdade, esse é o único caso que não cobri - porque pode ser.
mikeserv

18

Primeiro, o shell se expande ./*para todos os arquivos no diretório atual (exceto arquivos começando com um ponto).

  • se não houver ou houver apenas um arquivo: mvfalhar
  • se houver dois arquivos: o primeiro é movido para o segundo (que, portanto, se perde)
  • se houver mais de dois arquivos:
    • se o último for um diretório: todos os arquivos são movidos para este diretório
    • caso contrário, mvfalhará.

11
Obrigado. como saber qual subdiretório é "o último"?
Tim

7
Basta ligar para echo ./*ver qual ordem seu shell usa (geralmente em ordem alfabética).
Jofel

Se falhar com um arquivo, por que não diz isso?
Númeno

@ Noumenon Eu não entendo o seu comentário. mvretorna uma mensagem de erro se for chamada com apenas um argumento.
Jofel

Você está certo. O mesmo comando da minha história agora dá missing destination file operand after *filename*, embora ontem não tenha sido.
Noumenon

4

Quando você digita mv ./*, seu shell se expande ./*antes de executar mv.

Algumas coisas a serem observadas:

  • Se ./*for expandido para menos de 2 argumentos mv, logicamente produzirá um erro.
  • ./* normalmente será expandido para todos os arquivos (incluindo diretório) presentes no diretório atual e não começando com um ponto.
  • Você pode controlar o que se ./*expande lendo a documentação do seu shell ( man 7 globé um ponto de entrada para o tópico). Conchas diferentes terão opções diferentes.

1

O que mv *faz?

Aqui está uma resposta mais curta:

O shell expande o curinga *para uma lista do conteúdo do diretório. Em seguida, o shell passa a lista completa para o comando. O comando nunca vê *.

O comando mv file1 file2 ... filen directorymoverá file1 ... filen para o diretório

Exemplo

Aqui eu faço um diretório de teste contendo três arquivos

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

Você não pode mover vários arquivos em um único arquivo

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

Vamos adicionar um subdiretório

$ mkdir d

Você pode mover vários arquivos para um subdiretório

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

mv file1 file2 ... filen directoryÉ muito pouco provável que o comando tenha algo a ver com isso *.
mikeserv

@ Mike: Minha resposta aponta que o último estágio de expansão do shell efetivamente transforma o último no primeiro. Não saber que isso parece ser a raiz da confusão do OP.
RedGrittyBrick

Sim, mas o que quero dizer é que o shell não se expandirá *a file dirmenos que exista algum código de idioma no dseguinte f.
mikeserv

@mikeserv: Existe um código de idioma, meu exemplo é um recortar e colar do Putty se conectando a um sistema CentOS 5.6 GNU / Linux.
RedGrittyBrick

que local é esse? seu exemplo é o a b c dque não é file ... dir. Eu apenas comentei, porque você não menciona o tipo em nenhum lugar e diz apenas o que *se torna o file ... dirque não acontece.
mikeserv
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.