É muito mais fácil com zsh globs aqui:
for f (**/*.xml(.)) (mv -v -- $f **/$f:r:t(/[1]))
Ou se você quiser incluir arquivos xml ocultos e pesquisar em diretórios ocultos, como find :
for f (**/*.xml(.D)) (mv -v -- $f **/$f:r:t(D/[1]))
Mas tenha cuidado com o facto de os ficheiros chamados .xml , ..xml ou ...xml se tornarem um problema, pelo que poderá querer excluí-los:
setopt extendedglob
for f (**/(^(|.|..)).xml(.D)) (mv -v -- $f **/$f:r:t(D/[1]))
Com as ferramentas GNU, outra abordagem para evitar a varredura de toda a árvore de diretórios para cada arquivo seria varrê-lo uma vez e procurar todos os diretórios e xml arquivos, registrar onde eles estão e fazer a movimentação no final:
(export LC_ALL=C
find . -mindepth 1 -name '*.xml' ! -name .xml ! \
-name ..xml ! -name ...xml -type f -printf 'F/%PLC_ALL=C find . -type f -name '*.xml' ! -name .xml ! -name ..xml \
! -name ...xml -exec sh -c '
for file do
base=${file##*/}
base=${base%.xml}
escaped_base=$(printf "%s\n" "$base" |
sed "s/[[*?\\]/\\&/g"; echo .)
escaped_base=${escaped_base%??}
find . -name "$escaped_base" -type d -exec mv -v "$file" {\} \; -quit
done' sh {} +
' -o \
-type d -printf 'D/%Pfor f (**/*.xml(.)) (mv -v -- $f **/$f:r:t(/[1]))
' | awk -v RS='for f (**/*.xml(.D)) (mv -v -- $f **/$f:r:t(D/[1]))
' -F / '
{
if ($1 == "F") {
root = $NF
sub(/\.xml$/, "", root)
F[root] = substr($0, 3)
} else D[$NF] = substr($0, 3)
}
END {
for (f in F)
if (f in D)
printf "%ssetopt extendedglob
for f (**/(^(|.|..)).xml(.D)) (mv -v -- $f **/$f:r:t(D/[1]))
%s(export LC_ALL=C
find . -mindepth 1 -name '*.xml' ! -name .xml ! \
-name ..xml ! -name ...xml -type f -printf 'F/%PLC_ALL=C find . -type f -name '*.xml' ! -name .xml ! -name ..xml \
! -name ...xml -exec sh -c '
for file do
base=${file##*/}
base=${base%.xml}
escaped_base=$(printf "%s\n" "$base" |
sed "s/[[*?\\]/\\&/g"; echo .)
escaped_base=${escaped_base%??}
find . -name "$escaped_base" -type d -exec mv -v "$file" {\} \; -quit
done' sh {} +
' -o \
-type d -printf 'D/%P%pre%' | awk -v RS='%pre%' -F / '
{
if ($1 == "F") {
root = $NF
sub(/\.xml$/, "", root)
F[root] = substr($0, 3)
} else D[$NF] = substr($0, 3)
}
END {
for (f in F)
if (f in D)
printf "%s%pre%%s%pre%", F[f], D[f]
}' | xargs -r0n2 mv -v --
)
", F[f], D[f]
}' | xargs -r0n2 mv -v --
)
Sua abordagem tem vários problemas se você quiser permitir qualquer nome de arquivo arbitrário:
- a incorporação de
{}no código da shell está sempre errada. E se houver um arquivo chamado$(rm -rf "$HOME").xml, por exemplo? A maneira correta é passar o argumento{}as para o script de shell in-line (-exec sh -c 'use as "$1"...' sh {} \;). - Com o
finddo GNU (sugerido aqui porque você está usando-quit),*.xmlcorresponderia apenas aos arquivos que consistem em uma sequência de caracteres válidos seguidos por.xml, excluindo os nomes de arquivos que contêm caracteres inválidos no local atual (por exemplo, nomes de arquivos no charset errado). A correção para isso é para fixar a localidade emC, onde cada byte é um caractere válido (isso significa que as mensagens de erro serão exibidas em inglês). - Se algum desses arquivos
xmlfor do tipo diretório ou link simbólico, isso causaria problemas (afeta a verificação de diretórios ou quebra os links simbólicos quando movido). Você pode querer adicionar um-type fpara mover apenas arquivos regulares. - A substituição de comandos (
$(...)) retira todos caracteres de nova linha à direita. Isso causaria problemas com um arquivo chamadofoo.xml, por exemplo. Trabalhar em torno disso é possível, mas é uma dor:base=$(basename "$1" .xml; echo .); base=${base%??}. Você pode pelo menos substituirbasenamepelos operadores${var#pattern}. E evite a substituição de comandos, se possível. - seu problema com nomes de arquivos contendo caracteres curinga (
?,[,*e barra invertida; eles não são especiais para o shell, mas para a correspondência de padrões (fnmatch()) feita porfindque por acaso é muito semelhante à correspondência de padrões de shell). Você precisaria escapar deles com uma barra invertida. - o problema com
.xml,..xml,...xmlmencionado acima.
Então, se abordarmos todos os itens acima, acabamos com algo como:
%pre%Ufa ...
Agora, não é tudo. Com -exec ... {} + , executamos o mínimo de sh possível. Se tivermos sorte, executaremos apenas um, mas, se não, após a primeira invocação de sh , teremos movido vários arquivos xml e, em seguida, find continuaremos procurando por mais, e pode muito bem encontrar novamente os arquivos que movemos no primeiro turno (e provavelmente tentar movê-los para onde eles estiverem).
Além disso, é basicamente a mesma abordagem que os zshs. Algumas outras diferenças notáveis:
- com o
zshone, a lista de arquivos é classificada (por nome de diretório e nome de arquivo), portanto, o diretório de destino é mais ou menos consistente e previsível. Comfind, é baseado na ordem bruta de arquivos nos diretórios. - com
zsh, você receberá uma mensagem de erro se nenhum diretório correspondente para mover o arquivo for encontrado, não com a abordagemfindacima. - Com
find, você receberá mensagens de erro se alguns diretórios não puderem ser percorridos, não comzshone.
Uma última nota de aviso. Se o motivo pelo qual você obtém alguns arquivos com nomes de arquivos desonestos é porque a árvore de diretórios pode ser gravada por um adversário, então fique atento do que nenhuma das soluções acima seja segura se o adversário puder renomear arquivos sob os pés daquele comando.
Por exemplo, se você estiver usando o LXDE, o invasor pode criar um foo/lxde-rc.xml mal-intencionado, criar uma pasta lxde-rc , detectar quando você estiver executando o comando e substituir esse lxde-rc por um link simbólico para sua ~/.config/openbox/ durante a janela de corrida (que pode ser feita do tamanho necessário conforme necessário) entre find e encontrar lxde-rc e mv fazendo o rename("foo/lxde-rc.xml", "lxde-rc/lxde-rc.xml") ( foo também pode ser alterado para esse link simbólico mova seu lxde-rc.xml em outro lugar).
Trabalhando em torno disso é provavelmente impossível usando utilitários padrão ou mesmo GNU, você precisaria escrevê-lo em uma linguagem de programação apropriada, fazendo uma travessia de diretórios segura e usando renameat() chamadas do sistema.
Todas as soluções acima também falharão se a árvore de diretórios for profunda o suficiente para que o limite do comprimento dos caminhos dados à chamada do sistema rename() feita por mv seja atingido (fazendo com que rename() falhe com% código%). Uma solução usando ENAMETOOLONG também resolveria o problema.