Achatando um diretório aninhado


71

Provavelmente isso é muito simples, mas não consigo descobrir. Eu tenho uma estrutura de diretórios como esta (dir2 está dentro de dir1):

/dir1
    /dir2 
       |
        --- file1
       |
        --- file2

Qual é a melhor maneira de 'achatar' essa estrutura do diretor de forma a obter o arquivo1 e o arquivo2 no diretório1 e não no diretório2.


Respostas:


75

Você pode fazer isso com o GNU finde o GNU mv:

find /dir1 -mindepth 2 -type f -exec mv -t /dir1 -i '{}' +

Basicamente, da maneira que funciona, se isso findpercorre toda a árvore de diretórios e para cada arquivo ( -type f) que não está no diretório de nível superior ( -mindepth 2), ele executa um mvpara movê-lo para o diretório que você deseja ( -exec mv … +). O -targumento para mvpermite especificar o diretório de destino primeiro, o que é necessário porque a +forma de -execcoloca todos os locais de origem no final do comando. O -ifaz mvperguntar antes de substituir quaisquer duplicatas; você pode substituir -fpara substituí-los sem perguntar (ou -nnão perguntar ou substituir).

Como Stephane Chazelas aponta, o acima funciona apenas com ferramentas GNU (que são padrão no Linux, mas não na maioria dos outros sistemas). O seguinte é um pouco mais lento (porque chama mvvárias vezes), mas muito mais universal:

find /dir1 -mindepth 2 -type f -exec mv -i '{}' /dir1 ';'

3
Editado para usar -exec +para que ele não executa um grande número de processos demv
Random832

11
@ Random832 E vou reverter novamente, porque + não funciona. mvprecisa do destino como argumento final, mas + teria as fontes como argumento final. A descoberta nem aceita a sintaxe para a qual você a alterou ( find: missing argument to `-exec')
derobert 24/10

11
@ Random832, mas suponho que mvtenha um -tque possamos usar, então vou mudar para isso.
Derobert 24/10/12

11
@Dom findimprime arquivos ocultos (pontos) por padrão. A profundidade é relativa ao diretório que você passa para encontrar.
21813 derobert

11
Ou find ./dir -mindepth 2 -type f -exec mv -f '{}' ./dir ';'se substituir duplicatas
Atav32 22/01

33

No zsh:

mv dir1/*/**/*(.D) dir1

**/atravessa subdiretórios recursivamente. O qualificador global . corresponde apenas aos arquivos regulares e Dgarante que os arquivos de ponto sejam incluídos (por padrão, os arquivos cujo nome começa com a .são excluídos das correspondências curinga). Para limpar os diretórios agora vazios posteriormente, execute rmdir dir1/**/*(/Dod)- /restringe-se aos diretórios e odordena primeiro a profundidade das correspondências para remover dir1/dir2/dir3antes dir1/dir2.

Se o comprimento total dos nomes dos arquivos for muito grande, você poderá ter uma limitação no comprimento da linha de comando. O Zsh criou mve rmdirnão é afetado por esta limitação: execute zmodload zsh/filespara habilitá-los.

Com apenas ferramentas POSIX:

find dir1 -type f -exec mv {} dir1 \;
find dir1 -depth -exec rmdir {} \;

ou (mais rápido porque não precisa executar um processo separado para cada arquivo)

find dir1 -type f -exec sh -c 'mv "$@" dir1' _ {} +
find dir1 -depth -exec rmdir {} +

11
Esta deve ser a resposta aceita! Especialmente com a versão concisa do zsh.
9337 Adamski

3

Tente fazer isso:

cp /dir1/dir2/file{1,2} /another/place

ou para cada arquivo correspondente file[0-9]*ao subdir:

cp /dir1/dir2/file[0-9]* /another/place

Veja http://mywiki.wooledge.org/glob


Eu deveria ter indicado isso, mas tenho muitos arquivos para usar {}no meu problema real.
tartaruga

Veja minha segunda solução
Gilles Quenot

Bingo. Obrigado pela ajuda. Esta é definitivamente a melhor solução.
tartaruga

2

Eu escrevi duas funções que você pode usar juntas para fazer exatamente isso; você pode limitar o nível do diretório adicionando um -maxdepth $VALparâmetro.

# This scripts flattens the file directory
# Run this script with a folder as parameter:
# $ path/to/script path/to/folder

#!/bin/bash

rmEmptyDirs(){
    local DIR="$1"
    for dir in "$DIR"/*/
    do
        [ -d "${dir}" ] || continue # if not a directory, skip
        dir=${dir%*/}
        if [ "$(ls -A "$dir")" ]; then
            rmEmptyDirs "$dir"
        else
            rmdir "$dir"
        fi
    done
    if [ "$(ls -A "$DIR")" ]; then
        rmEmptyDirs "$DIR"
    fi
}

flattenDir(){
    local DIR="$1"
    find "$DIR" -mindepth 2 -type f -exec mv -i '{}' "$DIR" ';'
}

read -p "Do you wish to flatten folder: ${1}? " -n 1 -r
echo    # (optional) move to a new line
if [[ $REPLY =~ ^[Yy]$ ]]
then
    flattenDir "$1" &
    rmEmptyDirs "$1" &
    echo "Done";
fi

Cara, acabei de usar mal o seu script esquecendo o argumento do caminho, que realmente acabou com o meu servidor. Ok, eu sou o cara que copiar e colar coisas e desvio-los, mas caras, ser sábio e adicionar verificações / confirmações sobre scripts que excluir / mover animais assim ...
dulgan

Ops! Lamento ouvir isso. Espero que você tenha um backup ... Adicionei uma confirmação para proteção futura.
Bruno Bruno

Obrigado @Bruno, é muito melhor assim. Meu servidor ainda está funcionando perfeitamente, comentei a parte "achatar" para excluir diretórios vazios recursivamente da raiz (e esse foi o meu erro), até que vi um erro que me fez parar de executar o script.
dulgan

1

Expandindo a resposta popular para essa pergunta, já que eu tinha um caso de uso para achatar um diretório contendo arquivos com o mesmo nome.

dir1/
├── dir2
   └── file
└── dir3
    └── file

Nesse caso, a opção -i( --interactive) passada para mvnão produziria o resultado desejado para nivelar a estrutura de diretórios e lidar com conflitos de nomes. Portanto, é simplesmente substituído por --backup=t(equivalente a --backup=numbered). Mais documentação sobre a opção -b( --backup) disponível em https://www.gnu.org/software/coreutils/manual/coreutils.html#Backup-options .

Resultando em:

find dir1/ -mindepth 2 -type f -exec mv -t dir1/ --backup=t '{}' +

Qual produz:

dir1/
├── dir2
├── dir3
├── file
└── file.~1~

1

tar e zip têm a capacidade de incorporar e, em seguida, remover uma estrutura de diretório, então eu pude nivelar rapidamente um diretório aninhado com

tar -cvf all.tar *

seguida movendo all.tar para um novo local e, em seguida,

tar -xvf all.tar --strip=4

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.