Existe uma maneira de fazer com que o mv crie o diretório a ser movido se ele não existir?

275

Portanto, se eu estiver no meu diretório pessoal e desejar mover foo.c para ~ / bar / baz / foo.c, mas esses diretórios não existirem, existe alguma maneira de criar esses diretórios automaticamente, para que você só teria que digitar

mv foo.c ~/bar/baz/ 

e tudo daria certo? Parece que você poderia alias mv para um script bash simples que verificaria se esses diretórios existiam e, se não, chamaria mkdir e mv, mas pensei em verificar se alguém tinha uma idéia melhor.

Paul Wicks
fonte

Respostas:

294

Que tal esse one-liner (no bash):

mkdir --parents ./some/path/; mv yourfile.txt $_

Quebrando isso:

mkdir --parents ./some/path

cria o diretório (incluindo todos os diretórios intermediários), após o qual:

mv yourfile.txt $_

move o arquivo para esse diretório ($ _ se expande para o último argumento passado para o comando shell anterior, ou seja: o diretório recém-criado).

Não sei até que ponto isso funcionará em outros reservatórios, mas pode lhe dar algumas idéias sobre o que procurar.

Aqui está um exemplo usando esta técnica:

$ > ls
$ > touch yourfile.txt
$ > ls
yourfile.txt
$ > mkdir --parents ./some/path/; mv yourfile.txt $_
$ > ls -F
some/
$ > ls some/path/
yourfile.txt
KarstenF
fonte
32
Agradável com o '$ _'.
dmckee --- ex-moderador gatinho
1
Por que a obsessão com redundância em ./todos os lugares? Os argumentos não são comandos no caminho sem o .diretório ...
Jens
3
para os novatos, --parents pode ser encurtado para -p
sakurashinken
8
Estranhamente, --parentsnão está disponível no macOS 10.13.2. Você precisa usar -p.
Joshua Pinter
1
Não funciona se mover um arquivo, por exemplo ./some/path/foo. Nesse caso, o comando deve ser:mkdir -p ./some/path/foo; mv yourfile.txt $_:h
hhbilly 22/03/19
63
mkdir -p `dirname /destination/moved_file_name.txt`  
mv /full/path/the/file.txt  /destination/moved_file_name.txt
Billmc
fonte
2
Eu sou o único a perceber que esse código não faz sentido? Você cria recursivamente o caminho absoluto para o arquivo existente (o que significa que esse caminho já existe) e depois move o arquivo para um local (que no seu exemplo não existe).
Vdegenne 03/12
1
@ user544262772 O GNU coreutils ' dirnamenão requer que seu argumento exista, é pura manipulação de strings. Não posso falar por outras distribuições. Nada de errado com este código em questão.
TroyHurts
Esta é a resposta que é mais facilmente automatizada, eu acho. for f in *.txt; do mkdir -p `dirname /destination/${f//regex/repl}`; mv "$f" "/destination/${f//regex/repl}; done
Kyle
23

Salvar como um script chamado mv ou mv.sh

#!/bin/bash
# mv.sh
dir="$2"
tmp="$2"; tmp="${tmp: -1}"
[ "$tmp" != "/" ] && dir="$(dirname "$2")"
[ -a "$dir" ] ||
mkdir -p "$dir" &&
mv "$@"

Ou coloque no final do seu arquivo ~ / .bashrc como uma função que substitui o mv padrão em cada novo terminal. O uso de uma função permite que o bash mantenha a memória, em vez de precisar ler um arquivo de script todas as vezes.

function mv ()
{
    dir="$2"
    tmp="$2"; tmp="${tmp: -1}"
    [ "$tmp" != "/" ] && dir="$(dirname "$2")"
    [ -a "$dir" ] ||
    mkdir -p "$dir" &&
    mv "$@"
}

Estes foram baseados na submissão de Chris Lutz.

Sepero
fonte
3
Isso responde à pergunta com mais precisão IMHO. O usuário gostaria de um mvcomando aprimorado . Especialmente útil para scripts, ou seja, você não quer necessariamente executar as verificações e executar mkdir -psempre que precisar usar mv. Mas como eu desejaria o comportamento de erro padrão mv, mudei o nome da função para mvp- para que eu saiba quando poderia criar diretórios.
Brian Duncan
Parece a melhor idéia de solução, mas a implementação trava muito.
Ulises Layera
2
@UlisesLayera Modifiquei o algoritmo para torná-lo mais robusto. Ele não deve falhar agora
Sepero
Alguém poderia por favor explicar o significado de [ ! -a "$dir" ]eu conduzi experimentos com a metade direita sendo verdadeira e falsa, ambas avaliadas como verdadeiras ?? Pelo amor de outro, tmp = "$ {tmp: -1}" aparece para pegar o último caractere do arquivo para se certificar de que não é um caminho (/)
bazz
@azzazz Deveria estar fazendo "se $ dir não existir, então continue". Infelizmente, você está correto, há um erro ao usar "! -A" e não sei por que. Eu editei o código um pouco e agora deve funcionar.
Sepero
13

Você pode usar mkdir:

mkdir -p ~/bar/baz/ && \
mv foo.c ~/bar/baz/

Um script simples para fazê-lo automaticamente (não testado):

#!/bin/sh

# Grab the last argument (argument number $#)    
eval LAST_ARG=\$$#

# Strip the filename (if it exists) from the destination, getting the directory
DIR_NAME=`echo $2 | sed -e 's_/[^/]*$__'`

# Move to the directory, making the directory if necessary
mkdir -p "$DIR_NAME" || exit
mv "$@"
strager
fonte
"? $ Dirname ~ bar / baz /" Quando eu corro, eu recebo "/ home / dmckee / bar", que não é o que yo quer aqui ...
dmckee --- ex-moderador gatinho
@dmckee, Ah, você está certo. Alguma idéia de como resolver isso? Se você digitar ~ / bar / baz, (a) deseja copiar para ~ / bar / e renomeie para baz, ou (b) copie para ~ / bar / baz /. Qual é a melhor ferramenta para o trabalho?
223
@ DMCkee, eu usei regexp / sed para chegar a uma solução. Funciona ao seu gosto? =]
strager
Bom, exceto $ 2 não é o último argumento, a menos que $ # = 2. Eu tenho um programa, la, que imprime seu último argumento. Eu costumava usá-lo em uma versão do comando cp (para adicionar o diretório atual se o último argumento não fosse um diretório). Até conchas modernas suportam US $ 1 .. $ 9 apenas; um script Perl pode ser melhor.
31416 Jonathan Leffler
@ Leffler, Não é verdade - eu sei que o bash suporta mais de 9 argumentos (usar $ {123} é um método). Eu não conheço Perl, então fique à vontade para responder você mesmo. =]
strager
7

Parece que a resposta é não :). Eu realmente não quero criar um alias ou uma função apenas para fazer isso, geralmente porque é único e eu já estou no meio da digitação do mvcomando, mas encontrei algo que funciona bem para isso:

mv *.sh  shell_files/also_with_subdir/ || mkdir -p $_

Se mvfalhar (dir não existe), ele criará o diretório (que é o último argumento do comando anterior, o mesmo ocorre $_). Portanto, basta executar este comando, depois upexecutá-lo novamente, e desta vez mvdeve ter sucesso.

Pat
fonte
5

O seguinte script shell, talvez?

#!/bin/sh
if [[ -e $1 ]]
then
  if [[ ! -d $2 ]]
  then
    mkdir --parents $2
  fi
fi
mv $1 $2

Essa é a parte básica. Você pode adicionar um pouco para verificar se há argumentos e mudar o comportamento se o destino existir, ou o diretório de origem existir ou não existir (ou seja, não substituir algo que não existe) .

Chris Lutz
fonte
1
Com o seu código, a movimentação não é executada se o diretório não existir!
223
1
E você perdeu a bandeira "-p" para mkdir.
dmckee --- ex-moderador gatinho
Se um diretório não existe, por que criá-lo se você deseja movê-lo? (bandeira -p fixo)
Chris Lutz
Quero dizer, quando você executa o script e o diretório $ 2 não existe, ele é criado, mas o arquivo não é copiado.
Strager 13/02/09
ele deu uma construção simples e adicionou informações sobre como ela deveria ser expandida. Por que votar para baixo ?!
ypnos
5

O comando rsync pode fazer o truque somente se o último diretório no caminho de destino não existir, por exemplo, para o caminho de destino de ~/bar/baz/ifbar existir, mas baznão existir , o seguinte comando poderá ser usado:

rsync -av --remove-source-files foo.c ~/bar/baz/

-a, --archive               archive mode; equals -rlptgoD (no -H,-A,-X)
-v, --verbose               increase verbosity
--remove-source-files   sender removes synchronized files (non-dir)

Nesse caso baz diretório será criado se não existir. Mas se ambos bare baznão existirem, o rsync falhará:

sending incremental file list
rsync: mkdir "/root/bar/baz" failed: No such file or directory (2)
rsync error: error in file IO (code 11) at main.c(657) [Receiver=3.1.2]

Então, basicamente, deve ser seguro usar rsync -av --remove-source-filescomo um alias para mv.

sepehr
fonte
4

A maneira mais simples de fazer isso é:

mkdir [directory name] && mv [filename] $_

Vamos supor que eu baixei arquivos pdf localizados no meu diretório de downloads ( ~/download) e quero movê-los para um diretório que não existe (digamosmy_PDF ).

Digite o seguinte comando (certificando-me de que meu diretório de trabalho atual seja ~/download):

mkdir my_PDF && mv *.pdf $_

Você pode adicionar a -popção mkdirse desejar criar subdiretórios assim: (suponha que eu queira criar um subdiretório chamado python):

mkdir -p my_PDF/python && mv *.pdf $_
Abdoul Seibou
fonte
2

Sillier, mas trabalhando maneira:

mkdir -p $2
rmdir $2
mv $1 $2

Crie o diretório com mkdir -p, incluindo um diretório temporário que compartilhe o nome do arquivo de destino, remova esse diretório com um rmdir simples e mova o arquivo para o novo destino. Eu acho que a resposta usando dirname é provavelmente o melhor.

Blarn Blarnson
fonte
Sim, meio bobo. :-) Se $ 2 for um diretório (como na pergunta original), ele falhará miseravelmente, como o último diretório será excluído, então 'mv' falhará. E se $ 2 já existir, ele também tenta excluí-lo.
Tylla
Obrigado! Este é exatamente o que eu precisava para realizar: mv ./old ./subdir/new onde subdir ainda não existe
Mike Murray
2
((cd src-path && tar --remove-files -cf - files-to-move) | ( cd dst-path && tar -xf -))
svaardt
fonte
1

Código:

if [[ -e $1 && ! -e $2 ]]; then
   mkdir --parents --verbose -- "$(dirname -- "$2")"
fi
mv --verbose -- "$1" "$2"

Exemplo:

argumentos: "d1" "d2 / sub"

mkdir: created directory 'd2'
renamed 'd1' -> 'd2/sub'
John Doe
fonte
1

Isso será movido foo.cpara o novo diretório bazcom o diretório pai bar.

mv foo.c `mkdir -p ~/bar/baz/ && echo $_`

A -popção para mkdircriar diretórios intermediários, conforme necessário.
Sem-p todos os diretórios no prefixo do caminho, já deve existir.

Tudo dentro dos backticks ``é executado e a saída é retornada em linha como parte do seu comando.
Como mkdirnão retorna nada, apenas a saída de echo $_será adicionada ao comando.

$_referencia o último argumento ao comando executado anteriormente.
Nesse caso, ele retornará o caminho para o seu novo diretório ( ~/bar/baz/) passado para o mkdircomando.


Descompactei um arquivo compactado sem dar um destino e queria mover todos os arquivos, exceto demo-app.zipdo meu diretório atual para um novo diretório chamado demo-app.
A seguinte linha faz o truque:

mv `ls -A | grep -v demo-app.zip` `mkdir -p demo-app && echo $_`

ls -Aretorna todos os nomes de arquivos, incluindo arquivos ocultos ( exceto os implícitos .e.. ).

O símbolo de canal |é usado para canalizar a saída do lscomando grep( um utilitário de pesquisa em linha de comando e texto sem formatação ).
O -vsinalizador direciona greppara encontrar e retornar todos os nomes de arquivos, exceto demo-app.zip.
Essa lista de arquivos é adicionada à nossa linha de comando como argumentos de origem no comando move mv. O argumento target é o caminho para o novo diretório passado para o mkdirreferenciado using $_e output using echo.

Evan Wunder
fonte
1
Este é um bom nível de detalhes e explicações - e especialmente para um primeiro post. Obrigado!
Jeremy Caney
Obrigado, @ JeremyCaney! Estou animado para começar a ajudar!
Evan Wunder
0

Você pode até usar extensões de chaves:

mkdir -p directory{1..3}/subdirectory{1..3}/subsubdirectory{1..2}      
  • que cria 3 diretórios (diretório1, diretório2, diretório3),
    • e em cada um deles dois subdiretórios (subdiretório1, subdiretório2),
      • e em cada um deles dois subdiretórios (subsubdiretório1 e subsubdiretório2).

Você precisa usar o bash 3.0 ou mais recente.

Stefan Gruenwald
fonte
5
Interessante, mas não responde à pergunta do OP.
Alexey Feldgendler
0
$what=/path/to/file;
$dest=/dest/path;

mkdir -p "$(dirname "$dest")";
mv "$what" "$dest"
cab404
fonte
1
para torná-lo roteiro sh autônomo, basta substituir $ dest e US $ src com US $ 1 e US $ 2
cab404
0

Minha solução de uma string:

test -d "/home/newdir/" || mkdir -p "/home/newdir/" && mv /home/test.txt /home/newdir/
Andrey
fonte
0

Você pode encadear comandos: mkdir ~/bar/baz | mv foo.c ~/bar/baz/
ou o shell script está aqui:

#!/bin/bash
mkdir $2 | mv $1 $2

1. Abra qualquer editor de texto
2. Copie e cole o shell script.
3. Salve -o como mkdir_mv.sh
4. Digite o diretório do seu script: cd your/script/directory
5. Altere o modo do arquivo : chmod +x mkdir_mv.sh
6. Defina o alias : alias mv="./mkdir_mv.sh"
agora, sempre que você executar o comando, mvo diretório em movimento será criado, se não existir.

Sharofiddin
fonte
0

Com base em um comentário em outra resposta, aqui está a minha função shell.

# mvp = move + create parents
function mvp () {
    source="$1"
    target="$2"
    target_dir="$(dirname "$target")"
    mkdir --parents $target_dir; mv $source $target
}

Inclua isso no .bashrc ou similar para poder usá-lo em qualquer lugar.

Ben Winding
fonte