É 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
find
do GNU (sugerido aqui porque você está usando-quit
),*.xml
corresponderia 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
xml
for 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 f
para 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 substituirbasename
pelos 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 porfind
que por acaso é muito semelhante à correspondência de padrões de shell). Você precisaria escapar deles com uma barra invertida. - o problema com
.xml
,..xml
,...xml
mencionado 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
zsh
one, 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 abordagemfind
acima. - Com
find
, você receberá mensagens de erro se alguns diretórios não puderem ser percorridos, não comzsh
one.
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.