movendo arquivos que correspondam a um padrão que captura o diretório

movendo arquivos que correspondam a um padrão que captura o diretório

Considerar:

$ ls
foo  xyfooz.tex
$ find . -maxdepth 1 -type f -name '*foo*' -exec mv {} foo \;
$ ls ./foo*
xyfooz.tex

Estou tentando obter a mesma sequência de instruções apenas com mv, não com find:

$ ls
foo  xyfooz.tex
$ mv *foo* foo
mv: cannot stat '*foo*': No such file or directory
$ ls ./foo*
xyfooz.tex

Além disso (editar),

$ ls -F
foo/  xyfooz.tex*
$ mv -v *foo* foo

mv: cannot move 'foo' to a subdirectory of itself, 'foo/foo'
'xyfooz.tex' -> 'foo/xyfooz.tex'

Como garantir que o aviso 'stat' não apareça. Em outras palavras, eu acho que eu refinaria o padrão para limitar a fonte a arquivos e não a diretórios?

GNU bash, versão 4.3.48(1)-versão (x86_64-pc-linux-gnu)

$ cat /etc/os-release
NAME="Linux Mint"
VERSION="18.3 (Sylvia)"

Responder1

Commv

cannot move 'foo' to a subdirectory of itselfé inofensivo neste caso, você pode ignorá-lo. Quero dizer, mvmoveremos o resto apesar do aviso. Porém o status de saída não será 0, isso pode ser um grande obstáculo em scripts onde você deseja abortar ou executar uma ação de correção caso mvnão tenha sucesso.

Isso já foidisse em um comentário: você pode silenciar mvredirecionando stderr com 2>/dev/null; outros avisos ou erros também serão redirecionados.

Acho que você não pode facilmente "refinar o padrão para limitar a fonte a arquivos e não a diretórios". Ainda assim, você pode adotar uma abordagem que não corresponda ao literal foo. A abordagem a seguir não tem nada a ver com arquivos ou diretórios; apenas com nomes, porque os globos lidam com nomes; portanto, pode não ser suficiente em alguns casos.

O *foo*glob corresponde a quatro tipos disjuntivos de objetos:

  1. fooem si
  2. foo apenas com prefixo, como bar-foo- você pode combiná-los por*?foo
  3. foo com prefixo e postfix, como bar-foo-baz*?foo?*
  4. foo apenas com postfix, como foo-bazfoo?*

Para excluir foovocê precisa apenas dos três últimos (2-4), então a primeira abordagem pode ser:

mv *?foo *?foo?* foo?* foo/

A menos que você tenha a nullglobopção shell definida, qualquer padrão precisa corresponder a alguma coisa, caso contrário, será passado mvliteralmente. Você não quer isso. A solução é executar o seguinte comando previamente:

shopt -s nullglob

Esta opção de shell fará com que qualquer globo incomparável se expanda para nada. Aconselho você a pesquisar dotgloba opção também.

Observe que *?foo*corresponde a (2-3). Jogos semelhantes *foo?*(3-4). Além disso, considere --informar mvque todos os argumentos a seguir não são opções. Desta forma, um arquivo como --foonão será interpretado como uma opção (desconhecida). Isso leva a dois comandos melhores:

mv -- *?foo* foo?* foo/

ou

mv -- *?foo *foo?* foo/

Não importa qual você escolha. Apenas não use mv *?foo* *foo?* foo/; ele passará (por exemplo) bar-foo-bazpara mvduas vezes (ou seja, como dois argumentos separados), gerando assim um erro quando a ferramenta tentar mover este objeto pela segunda vez.

Quando nenhuma correspondência for encontrada, meus mvcomandos se degenerarão mv foo/e gerarão um erro. Seu mvcomando original (com nullglobunset) obteria o literal *foo*e geraria outro erro.


Exemplo de sessão de console:

$ shopt -s nullglob
$ mkdir foo
$ touch bar-foo bar-foo-baz foo-baz xyfooz.tex ./--foo
$ mv -v -- *?foo* foo?* foo/
'bar-foo' -> 'foo/bar-foo'
'bar-foo-baz' -> 'foo/bar-foo-baz'
'--foo' -> 'foo/--foo'
'xyfooz.tex' -> 'foo/xyfooz.tex'
'foo-baz' -> 'foo/foo-baz'
$ 

Hack simples

Digamos dummyque não existe.

mv foo dummy
mv *foo* dummy/
mv dummy foo

A renomeação de um diretório deve ser muito rápida em sistemas de arquivos baseados em inode. Programas que possuem arquivos foo/já abertos não devem interferir nem quebrar porque é o inode que importa. No entanto, se algum programa precisar abrir um arquivo (incluindo seu caminho foo/) entre o primeiro e o terceiro mv, ele falhará. Outros efeitos colaterais podem ocorrer (imagine um programa de terceiros recriando foo/enquanto isso), é por isso que chamo essa abordagem de hack. Pense duas vezes antes de usá-lo.


Com findqualquer maneira

Separar arquivos de diretórios é uma tarefa para find. O que você fez findfoi basicamente o caminho certo. O que você quer fazer (e o que eu fiz acima) com mvshell globbing parece... bem, "menos certo", no máximo. Este é o seu comando:

find . -maxdepth 1 -type f -name '*foo*' -exec mv {} foo \;

Isso findexecutará um arquivo separado mvpara cada arquivo correspondente; todo o comando terá um desempenho ruim. Por esse motivo, pode-se querer mudar para um único arquivo mv. Se esta é a sua linha de pensamento (e você mvé findrico o suficiente), então você deve considerar esta maneira "muito correta":

find . -maxdepth 1 -type f -name '*foo*' -exec mv -t foo/ -- {} +

As vantagens sobre o seu findcomando e/ou simples mvcom glob(s):

  • um único mvpode ter vários arquivos para trabalhar;
  • ainda assim, se houver muitos arquivos para serem manipulados por uma linha de comando, findserão executados mvprocessos adicionais;
  • no caso em que nenhum arquivo corresponda ao padrão, mvnão será executado;
  • graças a --um arquivo como --foonão será interpretado como uma opção (desconhecida) para mv;

informação relacionada