Como posso nivelar um diretório no seguinte formato?
Antes: ./aaa/bbb/ccc.png
Depois de: ./aaa-bbb-ccc.png
./foo/bar/baz.png
e ./foo-bar-baz.png
ambos existem. Presumo que você não queira substituir o último pelo primeiro?
Como posso nivelar um diretório no seguinte formato?
Antes: ./aaa/bbb/ccc.png
Depois de: ./aaa-bbb-ccc.png
./foo/bar/baz.png
e ./foo-bar-baz.png
ambos existem. Presumo que você não queira substituir o último pelo primeiro?
Respostas:
Aviso: digitei a maioria desses comandos diretamente no meu navegador. Advertência.
Com zsh e zmv :
zmv -o -i -Qn '(**/)(*)(D)' '${1//\//-}$2'
Explicação: O padrão **/*
corresponde a todos os arquivos nos subdiretórios do diretório atual, recursivamente (não corresponde aos arquivos no diretório atual, mas estes não precisam ser renomeados). Os dois primeiros pares de parênteses são grupos que podem ser referidos como $1
e $2
no texto de substituição. O par final de parênteses adiciona o D
qualificador glob para que os arquivos de ponto não sejam omitidos. -o -i
significa passar a -i
opção para, mv
para que você seja solicitado se um arquivo existente será substituído.
Com apenas ferramentas POSIX:
find . -depth -exec sh -c '
for source; do
case $source in ./*/*)
target="$(printf %sz "${source#./}" | tr / -)";
mv -i -- "$source" "${target%z}";;
esac
done
' _ {} +
Explicação: a case
instrução omite o diretório atual e os subdiretórios de nível superior do diretório atual. target
contém o nome do arquivo de origem ( $0
) com o primeiro destacado ./
e todas as barras substituídas por traços, além de uma final z
. A final z
está lá, caso o nome do arquivo termine com uma nova linha: caso contrário, a substituição do comando o removeria.
Se o seu find
não suporta -exec … +
(OpenBSD, estou olhando para você):
find . -depth -exec sh -c '
case $0 in ./*/*)
target="$(printf %sz "${0#./}" | tr / -)";
mv -i -- "$0" "${target%z}";;
esac
' {} \;
Com o bash (ou ksh93), você não precisa chamar um comando externo para substituir as barras por traços, você pode usar a expansão de parâmetro ksh93 com a construção de substituição de cadeia ${VAR//STRING/REPLACEMENT}
:
find . -depth -exec bash -c '
for source; do
case $source in ./*/*)
source=${source#./}
target="${source//\//-}";
mv -i -- "$source" "$target";;
esac
done
' _ {} +
for source
exemplos perdem o primeiro arquivo / diretório apresentado ... Finalmente descobri por que você (normalmente) usou _
como $0
:) ... e obrigado pelas ótimas respostas. Eu aprendo muito com eles.
Embora já existam boas respostas aqui, acho isso mais intuitivo em bash
:
find aaa -type f -exec sh -c 'new=$(echo "{}" | tr "/" "-" | tr " " "_"); mv "{}" "$new"' \;
Onde aaa
está o diretório existente. Os arquivos que ele contém serão movidos para o diretório atual (deixando apenas diretórios vazios no aaa). As duas chamadas para tr
lidar com diretórios e espaços (sem lógica para barras escapadas - um exercício para o leitor).
Considero isso mais intuitivo e relativamente fácil de ajustar. Ou seja, eu poderia alterar os find
parâmetros se disser que quero encontrar apenas arquivos .png. Posso alterar as tr
chamadas ou adicionar mais conforme necessário. E posso acrescentar echo
antes mv
se quiser verificar visualmente se ele fará a coisa certa (depois repita sem o extra echo
apenas se as coisas parecerem corretas:
find aaa -name \*.png -exec sh -c 'new=$(echo "{}" | tr "/" "-" | tr " " "_"); echo mv "{}" "$new"' \;
Observe também que estou usando find aaa
... e não find .
... ou find aaa/
..., pois os dois últimos deixariam artefatos engraçados no nome final do arquivo.
find . -mindepth 2 -type f -name '*' |
perl -l000ne 'print $_; s/\//-/g; s/^\.-/.\// and print' |
xargs -0n2 mv
Nota: isso não funcionará para o nome do arquivo que contém \n
.
Isso, é claro, move apenas f
arquivos de tipo ...
Os únicos conflitos de nome seriam de arquivos pré-existentes no pwd
Testado com este subconjunto básico
rm -fr junk
rm -f junk*hello*
mkdir -p junk/junkier/junkiest
touch 'hello hello'
touch 'junk/hello hello'
touch 'junk/junkier/hello hello'
touch 'junk/junkier/junkiest/hello hello'
Resultando em
./hello hello
./junk-hello hello
./junk-junkier-hello hello
./junk-junkier-junkiest-hello hello
Aqui está uma ls
versão .. comentários são bem-vindos. Ele funciona para nomes de arquivos malucos como esse
"j1ळ\n\001\n/j2/j3/j4/\nh h\n"
, mas estou realmente interessado em saber de alguma armadilha. É mais de um exercício de "pode o caluniado ls
realmente fazê-lo (robusta)?"
ls -AbQR1p * | # paths in "quoted" \escaped format
sed -n '/":$/,${/.[^/]$/p}' | # skip files in pwd, blank and dir/ lines
{ bwd="$PWD"; while read -n1; # save base dir strip leading "quote
read -r p; do # read path
printf -vs "${p%\"*}" # strip trailing quote"(:)
[[ $p == *: ]] && { # this is a directory
cd "$bwd/$s" # change into new directory
rnp="${s//\//-}"- # relative name prefix
} || mv "$s" "$bwd/$rnp$s"
done; }
Basta canalizar o nome do arquivo + caminho para o tr
comando.tr <replace> <with>
for FILE in `find aaa -name "*.png"`
do
NEWFILE=`echo $FILE | tr '/' '-'`
cp -v $FILE $NEWFILE
done
cp
linha para cp -v "$FILE" "$NEWFILE"
ajudar? (Além disso, você não deve assumir apenas os arquivos PNG.)
cp
linha é insuficiente. Toda a abordagem de analisar a find
saída com um for
loop é falha. As únicas maneiras seguras de usar find
são com -exec
, ou -print0
se disponíveis (menos portáteis que -exec
). Sugiro uma alternativa mais robusta, mas sua pergunta está subespecificada no momento.
Seguro para espaços em nomes de arquivos:
#!/bin/bash
/bin/find $PWD -type f | while read FILE
do
_new="${FILE//\//-}"
_new="${_new:1}"
#echo $FILE
#echo $_new
/bin/mv "$FILE" "$_new"
done
Funcionou com o meu em cp
vez de mv
por razões óbvias, mas deve produzir o mesmo.
type find
-> find is /usr/bin/find
na minha máquina. Usar PATH
como nome de variável em um script é uma péssima idéia. Além disso, seu script manipula os nomes de arquivos com espaços ou barras invertidas, porque você usa o read
comando incorretamente (pesquise neste site IFS read -r
).
find is hashed (/bin/find)
, relevante como? Distros diferentes são diferentes?
PATH
variável de ambiente, que é algo que todo sistema Unix usa. Se você escrever /bin/find
, seu script será realmente quebrado em todos os sistemas (Gilles, meu e provavelmente o OP) que não possui /bin/find
(por exemplo, b / c está /usr/bin/find
). O ponto principal da PATH
variável de ambiente é tornar isso um problema.
$(pwd)
já está disponível em uma variável: $PWD
. Mas você não deve usar um caminho absoluto aqui: se seu script for invocado, digamos /home/tim
, ele tenta renomear /home/tim/some/path
para /home-tim-some-path
.