PS: Por favor, também são responsáveis por nomes de arquivos com caracteres estranhos (por exemplo, espaços)
Nathan JB
3
Você também vai precisar para especificar como você deseja lidar com o caso possível, onde ./foo/bar/baz.pnge ./foo-bar-baz.pngambos existem. Presumo que você não queira substituir o último pelo primeiro?
Jw013
No meu caso, é irrelevante, mas a resposta deve de fato explicar isso para que outros possam confiar nela! Obrigado!
Nathan JB
Respostas:
7
Aviso: digitei a maioria desses comandos diretamente no meu navegador. Advertência.
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 $1e $2no texto de substituição. O par final de parênteses adiciona o Dqualificador glob para que os arquivos de ponto não sejam omitidos. -o -isignifica passar a -iopção para, mvpara 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 caseinstrução omite o diretório atual e os subdiretórios de nível superior do diretório atual. targetconté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 zestá lá, caso o nome do arquivo termine com uma nova linha: caso contrário, a substituição do comando o removeria.
Se o seu findnã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
' _ {} +
Os for sourceexemplos 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.
Peter.O
Observe que o exemplo zmv apenas executa uma execução a seco: imprime os comandos de movimentação, mas não os executa. Para realmente executar esses comandos, remova o n em -Qn.
Peter
5
Embora já existam boas respostas aqui, acho isso mais intuitivo em bash:
Onde aaaestá 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 trlidar 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 findparâmetros se disser que quero encontrar apenas arquivos .png. Posso alterar as trchamadas ou adicionar mais conforme necessário. E posso acrescentar echoantes mvse quiser verificar visualmente se ele fará a coisa certa (depois repita sem o extra echoapenas se as coisas parecerem corretas:
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.
Obrigado por este liner - isso funcionou bem para mim quando o fiz fora do diretório (que é exatamente o que você disse para fazer). Quando tentei fazer de dentro do diretório (esperando evitar que o nome do diretório raiz fosse adicionado) todos os arquivos "desapareceram". Eu os havia transformado em arquivos ocultos no mesmo diretório.
Nota: isso não funcionará para o nome do arquivo que contém \n.
Isso, é claro, move apenas farquivos de tipo ...
Os únicos conflitos de nome seriam de arquivos pré-existentes no pwd
Aqui está uma lsversã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 lsrealmente 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; }
Isso não lida com nomes de arquivos estranhos com segurança. Os espaços vão quebrá-lo (entre outras coisas).
Jw013
À primeira vista, esta é uma solução limpa e legível, mas @ jw013 está certo, ele não manipula bem os espaços. Mudar a cplinha para cp -v "$FILE" "$NEWFILE"ajudar? (Além disso, você não deve assumir apenas os arquivos PNG.)
Nathan JB
2
@ NathanJ.Brauer A adição de aspas à cplinha é insuficiente. Toda a abordagem de analisar a findsaída com um forloop é falha. As únicas maneiras seguras de usar findsão com -exec, ou -print0se disponíveis (menos portáteis que -exec). Sugiro uma alternativa mais robusta, mas sua pergunta está subespecificada no momento.
Jw013
-2
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 cpvez de mvpor razões óbvias, mas deve produzir o mesmo.
type find-> find is /usr/bin/findna minha máquina. Usar PATHcomo 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 readcomando incorretamente (pesquise neste site IFS read -r).
Gilles 'SO- stop be evil'
find is hashed (/bin/find), relevante como? Distros diferentes são diferentes?
Tim
Sabia que eu deveria ter ficado longe disso, isca de abutre.
Tim
@ Tim Você sempre pode excluir a resposta para obter seu representante de volta (e o meu também porque custa rep para votar). A codificação dos caminhos dos scripts parece sugerir que você está perdendo o ponto da PATHvariá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 PATHvariável de ambiente é tornar isso um problema.
Jw013
By the way, $(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/pathpara /home-tim-some-path.
./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 :
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 oD
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:
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 finalz
. A finalz
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ê):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}
:fonte
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
: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 paratr
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 astr
chamadas ou adicionar mais conforme necessário. E posso acrescentarecho
antesmv
se quiser verificar visualmente se ele fará a coisa certa (depois repita sem o extraecho
apenas se as coisas parecerem corretas:Observe também que estou usando
find aaa
... e nãofind .
... oufind aaa/
..., pois os dois últimos deixariam artefatos engraçados no nome final do arquivo.fonte
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
Resultando em
fonte
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 caluniadols
realmente fazê-lo (robusta)?"fonte
Basta canalizar o nome do arquivo + caminho para o
tr
comando.tr <replace> <with>
fonte
cp
linha paracp -v "$FILE" "$NEWFILE"
ajudar? (Além disso, você não deve assumir apenas os arquivos PNG.)cp
linha é insuficiente. Toda a abordagem de analisar afind
saída com umfor
loop é falha. As únicas maneiras seguras de usarfind
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:
Funcionou com o meu em
cp
vez demv
por razões óbvias, mas deve produzir o mesmo.fonte
type find
->find is /usr/bin/find
na minha máquina. UsarPATH
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 oread
comando incorretamente (pesquise neste siteIFS 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 daPATH
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
.