Script de shell UNIX: como mover recursivamente os arquivos para cima em um diretório?

8

Eu tenho um grande número de arquivos pequenos, f, organizados em uma estrutura de diretórios da seguinte maneira:

/A/B/C/f

Existem 11 diretórios no nível A, cada um com cerca de 100 diretórios no nível de B, cada um com cerca de 30 diretórios no nível de C, cada um com um arquivo f.

Como movo todos os arquivos para um nível acima? Por exemplo, dado esse conjunto de arquivos ...

/ A / B / C / f1
/ A / B / C / f2
/ A / B / C / f3
/ A / B / C / f4

Eu quero que o diretório /A/B/contenha 4 arquivos, f1 a f4. A remoção de diretórios C não é necessária.

Eu estou esperando que este é um problema resolvido, possivelmente envolvendo find, xargse whatnot. Alguma ideia?

Felicidades,

James

jl6
fonte

Respostas:

9

É bastante simples com o GNU find (como encontrado no Linux) ou qualquer outro achado que suporte -execdir:

find A -type f -execdir mv -i {} .. \;

Com um padrão find:

find A -type f -exec sh -c 'mv -i "$1" "${1%/*}/.."' sh {} \;

Com zsh:

zmv -Q -o-i 'A/(**/)*/(*)(.)' 'A/$1$2'

Se a estrutura de diretórios sempre tiver o mesmo nível de aninhamento, você não precisará de nenhuma passagem recursiva (mas remova primeiro os diretórios vazios):

for x in */*; do; echo mv -i "$x"/*/* "$x"/..; done
Gilles 'SO- parar de ser mau'
fonte
1

Para esse conjunto de arquivos, isso faria:

$ cd /A/B/C/
$ mv ./* ../

Mas estou esperando que o seu problema seja um pouco mais complicado ... Não posso responder a isso ... Não tenho muita certeza de como está a estrutura do seu diretório ... Você poderia esclarecer?

Estouro de pilha está morto
fonte
Obviamente, isso funcionará para qualquer diretório / A / B / C, mas como tenho cerca de 30000 desses diretórios, espero um comando que possa ser executado no topo da hierarquia para cuidar de todos de uma só vez.
Jl6
1
@ jl6 eu vejo, e apenas arquivos precisam ser movidos? Portanto, C não precisa se mover para B e B não para A, etc ...
Stack Overflow is dead
Está correto - a operação deve ser realizada apenas em arquivos, no nível mais baixo da hierarquia, e eles devem ser movidos apenas um nível acima.
jl6
0

Meu primeiro palpite é

$ find A -type f -exec mv {} .. \;  

contanto que você não especifique, -depthtudo ficará bem. Eu não tentei, então teste primeiro antes de se comprometer.

hotei
fonte
O que o find faz quando existem dois arquivos com o mesmo nome?
Encontrar não é o problema, o comando "mv" incorporado é. Nomes enganados não seriam bons, pois mv não vai renomear, ele será sobrescrito. Você pode usar o mv -i para ajudar um pouco, mas com mais de 30.000 arquivos que podem ser dolorosos. Se você precisa ter tanto controle sobre a situação, pode precisar de um programa escrito em uma linguagem de script (minha escolha seria python, YMMV).
hotei
Eu não usei "backups" com o mv, mas, olhando para a página de manual, parece que isso pode ser uma solução para o seu problema de nomes falsos. msgstr "encontre A -type f -exec mv - backup numerado {} .. \;" trabalho poder (note que é um traço duplo em --backup)
hotei
O exemplo de localização fornecido moverá todos os arquivos no nível mais baixo para um diretório acima de A (acredito que o ".." está sendo interpretado pelo shell antes de ser passado para localizar / mv?). Eu preciso que os arquivos subam apenas um nível na hierarquia. Mais um esclarecimento: não haverá nomes de arquivos duplicados, desde que os arquivos subam apenas UM nível acima.
jl6
@hotei: Os mvcomandos são executados no diretório atual, portanto ..não fazem o que você espera. Veja minha resposta para soluções. @ jl6: o shell não tem nada a ver com ..isso, é tratado pelo kernel.
Gilles 'SO- stop be evil'
0

Se você deseja mover apenas os arquivos que estão nos diretórios folha (ou seja, não deseja mover /A/B/filepara /Ase Bcontém subdiretórios), aqui estão algumas maneiras de fazer isso:

Ambos exigem isso

leaf ()
{
    find $1 -depth -type d | sed 'h; :b; $b; N; /^\(.*\)\/.*\n\1$/ { g; bb }; $ {x; b}; P; D'
}
shopt -s nullglob

Este funciona:

leaf A | while read -r dir
do
    for file in "$dir"/*
    do
        parent=${dir%/*}
        if [[ -e "$parent/${file##*/}" ]]
        then
            echo "not moved: $file"
        else
            mv "$file" "$parent"
        fi
    done
done

Isso será mais rápido, mas não gosta de diretórios de código-fonte vazios:

leaf A | while read -r dir
do
    mv -n "${dir}"/* "${dir%/*}"
    remains=$(echo "$dir"/*)
    [[ -n "$remains" ]] && echo "not moved: $remains"
done
Pausado até novo aviso.
fonte