mover arquivos correspondentes a um padrão que capture o diretório

0

Considere:

$ 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 encontrar:

$ 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, eu refinaria o padrão para limitar a fonte aos arquivos e não aos diretórios?

GNU bash, 4.3.48 (1) -release (x86_64-pc-linux-gnu)

$ cat /etc/os-release
NAME="Linux Mint"
VERSION="18.3 (Sylvia)"
    
por Erwann 16.04.2018 / 02:01

1 resposta

1

com mv

cannot move 'foo' to a subdirectory of itself é inofensivo neste caso, você pode ignorá-lo. Quero dizer mv vai mover o resto apesar do aviso. No entanto, o status de saída não será 0 , isso pode ser um grande obstáculo em scripts nos quais você deseja interromper ou executar uma ação de correção se mv não for bem-sucedido.

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

Eu acho que você não pode facilmente "refinar o padrão para limitar a fonte a arquivos e 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 globs lidam com nomes; então pode não ser suficiente em alguns casos.

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

  1. foo em 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-baz - foo?*

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

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

A menos que você tenha a opção nullglob shell, qualquer padrão precisa corresponder a algo, caso contrário, ele será passado para mv literalmente. Você não quer isso. A solução é executar o seguinte comando de antemão:

shopt -s nullglob

Esta opção de shell fará com que qualquer glob incomparável seja expandido para nada. Eu aconselho você a pesquisar a opção dotglob também.

Observe que *?foo* corresponde (2-3). Da mesma forma *foo?* corresponde (3-4). Além disso, considere -- informar mv de que todos os argumentos a seguir não são opções. Dessa forma, um arquivo como --foo nã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ê escolher. Apenas não use mv *?foo* *foo?* foo/ ; ele passará (por exemplo) bar-foo-baz para mv duas 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 comandos mv se degenerarão para mv foo/ e lançarão um erro. Seu comando mv original (com nullglob unset) obteria o literal *foo* e lançaria outro erro.

Exemplo de sessão do 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 que dummy não exista.

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

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

Com find de qualquer maneira

A inclusão de arquivos de diretórios é um trabalho para find . O que você fez com find é basicamente o caminho certo. O que você quer fazer (e o que eu fiz acima) com mv e shell globbing parece ... bem, "menos certo" no máximo. Este é o seu comando:

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

Este find executará um mv separado para cada arquivo correspondente, todo o comando terá um desempenho ruim. Por esse motivo, pode-se querer mudar para um único mv . Se esta é sua linha de pensamento (e seus mv e find são ricos o suficiente), então você deve considerar este modo "muito correto":

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

As vantagens sobre o comando find e / ou sobre mv sem formatação com glob (s):

  • um único mv pode ter vários arquivos para trabalhar;
  • ainda, se houver muitos arquivos para uma linha de comando, find executará mv process (es) adicionais;
  • em um caso em que nenhum arquivo corresponde ao padrão, mv não será executado;
  • obrigado a -- um arquivo como --foo não será interpretado como uma opção (desconhecida) para mv ;
por 02.05.2018 / 00:59