Como renomear certos diretórios se o pai tiver um nome específico?

1

Tenho que fazer uma renomeação global em um projeto de plug-in do Eclipse, embora o fato de o código ser Java e ser um plug-in do Eclipse seja irrelevante.

Basicamente, os passos que preciso executar são os seguintes:

  1. find . -name "*" -type f | grep -v \.git | xargs sed -i 's/com.foo/org.bar/g
  2. para todos os diretórios (excluindo .git ) em src/com , renomeie para src/org
  3. para todos os diretórios (excluindo .git ) em src/org/foo , renomeie para src/org/bar
  4. para todos os nomes de arquivos e diretórios (excluindo .git) com com.foo no nome, altere essa parte para org.bar .

Eu só tenho uma linha de comando explícita para o primeiro passo.

Eu consegui executar as outras etapas semi- manualmente, mas acho que valeria a pena determinar um método automatizado para fazer isso. O segundo e terceiro passos são equivalentes, mas o quarto passo é diferente.

Eu quero fazer isso com a linha de comando pura do bash, mas eu poderia me contentar com um simples script bash ou perl.

Atualizar :

Na verdade, preciso pesquisar mais do que apenas src/com . Eu também preciso de src/main/java/com e src/test/java/com .

Para isso, tenho experimentado algo assim:

find . -type d -name "com" | grep "\(/src/com\|/src/main/java/com\|/src/test/java/com\)" | xargs -n1 -i{} echo mv {} $(echo {} | sed -e 's/com/org/g')

Isso fica próximo, mas o último sed não está correspondendo, então não muda. Resulta em linhas como esta:

mv ./plugins/.../src/com ./plugins/.../src/com
    
por David M. Karr 04.05.2016 / 19:48

2 respostas

1

1

Para sua primeira etapa, no seu exemplo de comando, você não precisa do * porque ele não está filtrando nenhum arquivo. O que você precisa é filtrar .git , como este:

$ find . -name '.git' -prune -o -type f -print

Isso rejeitará ( -prune ) qualquer diretório com o mesmo nome .git e tudo dentro dele.

Isso elimina a necessidade de grep -v ".git" . O sed não poderia ser evitado, já que está editando os componentes internos de um arquivo, algo que a localização não poderia fazer.

Mas ainda assim, podemos simplificar (citando o ponto dentro do sed):

$ find . -name '.git' -prune -o -type f -exec sed -i 's/com\.foo/org\.bar/g' '{}' \+

Isso acumulará nomes de arquivos (semelhantes a xargs) ao comando sed .

Mas ainda melhor:

$ find . -name '.git' -prune -o -type f -execdir sed -i 's/com\.foo/org\.bar/g' '{}' \+

Que executará uma instância de sed por diretório.

2 e 3

Vamos definir uma função para executar a renomeação de dir:

$ renamedir (){ find "$1" -name '.git' -prune -o -type d -exec bash -c 'mv "$1" "${1//"$2"/$3}"' sh '{}' "$2" "$3" \; ; }

Etapa 2:

  • para todos os diretórios (excluindo .git) em src / com renomear para src / org

    $ renamedir 'd/src/com' 'src/com' 'src/org'
    

Etapa 3:

  • para todos os diretórios (excluindo .git) em src / org / foo renomear para src / org / bar

    $ renamedir 'src/org/foo' 'ogr/foo' 'org/bar'
    

4

Etapa 4:

  • para todos os nomes de arquivos e diretórios (excluindo .git) com "com.foo" no nome, altere essa parte para "org.bar".

    $ find . -name '.git' -prune -o -name "com.foo" -type f -exec bash -c 'mv "$1" "${1//"$2"/$3}"' sh '{}' "com.foo" "org.bar" \; ; }
    

Tudo dentro de um script (melhor formatado):

#!/bin/bash

# NOTE: There are three echo to avoid execution of commands.
#       If after testing, you decide that it is ok to execute commands,
#       remove the echo words.

# Step 1:
### for all files (excluding .git) in . change
###     file contents from com.foo to org.bar
find . -name '.git' -prune -o -type f -execdir echo sed -i 's/com\.foo/org\.bar/g' '{}' \+

renamedir (){ find "$1" -name '.git' -prune -o -type d -exec bash -c '
                  echo \
                  mv "$1" "${1//"$2"/$3}"
              ' sh '{}' "$2" "$3" \;
        }

# Step 2:
### for all directories (excluding .git) in src/com rename to src/org
renamedir 'd/src/com' 'src/com' 'src/org'

# Step 3:
### for all directories (excluding .git) in src/org/foo rename to src/org/bar
renamedir 'src/org/foo' 'org/foo' 'org/bar'

# Step 4:
### for all file and directory names (excluding .git)
### with "com.foo" in the name, change that portion to "org.bar".
find . -name '.git' -prune -o -name "*com.foo*" -type f -exec bash -c '
    echo \
    mv "$1" "${1//"$2"/$3}"
' sh '{}' "com.foo" "org.bar" \; ; }

Para testar as diferentes versões de correspondência, crie uma árvore inteira de arquivos como esta (remova-a depois com rm -rf ./d/ ):

$ mkdir -p d/src/{{,.git}{,one},com/{{,.git}{,two},\ .git,.git/six},org/{{,.git}{,three},bar/{,.git}{,four},foo/{,.git}{,five}}}
$ touch d/src/{111,com/{222,.git/666},org/{333,bar/444,foo/555}}  
$ touch d/src/{.git/{aaa,one/aaaaa},one/aaa111,com/.git{/bbb,two/bbb222},org/{.git{/ccc,three/ccc333},bar/.git{/ddd,four/ddd444},foo/.git{/eee,five/eee555}}}               

Seu comando original (usando a opção GNU -z para encontrar find -print0) irá corresponder (neste diretório):

$ find d/ -type f -print0 | grep -zZv \.git
find d/ -type f -print0 | grep -zZv \.git | xargs -0 printf '<%s>\n'
<d/src/org/333>
<d/src/org/foo/555>
<d/src/org/bar/444>
<d/src/com/222>
<d/src/111>

Apenas cinco arquivos, aqueles que não têm relação com nada .git .

Em geral:

Existem três maneiras principais de corresponder .git em find : -name , -regex e -path . Deixando de lado -regex que além de ser ímpar (usa emacs regex) é necessário apenas para combinações complexas que aqui não são necessárias.

Um find d/ -path "./.git" corresponderá exatamente a isso: somente um diretório .git de inmediate.
Ou (para o diretório criado) find . -path './d/src/.git' corresponderá apenas a ./d/src/.git

Um find d/ -name ".git" corresponderá exatamente a .git no último nível (basename) de uma árvore.

Tanto -name "*.git" quanto -path "*.git" serão exatamente iguais.
Qualquer string que termine em .git .

O que começa a ficar complexo é quando adicionamos dois asteriscos: *.git*
Nesse caso, -name corresponderá apenas ao último nível do caminho (o nome base do arquivo), mas devido aos dois asteriscos * também corresponderá aos nomes com prefixo (observe o espaço em .git ), e postfix (note muitos como .gitone ):

$ find d -name '*.git*'
d/src/org/.gitthree
d/src/org/foo/.git
d/src/org/foo/.gitfive
d/src/org/bar/.gitfour
d/src/org/bar/.git
d/src/org/.git
d/src/com/.gittwo
d/src/com/.git
d/src/com/ .git
d/src/.git
d/src/.gitone

corresponderá a .git em qualquer nível do caminho, quase como grep funciona. Não é exatamente o mesmo que grep, pois o grep poderia ter um Regex complexo, mas aqui a correspondência é contra um simples "padrão". Observe o d/src/com/.git/six abaixo:

$ find d -path '*.git*'
d/src/org/.gitthree
d/src/org/foo/.git
d/src/org/foo/.gitfive
d/src/org/bar/.gitfour
d/src/org/bar/.git
d/src/org/.git
d/src/com/.gittwo
d/src/com/.git
d/src/com/.git/six
d/src/com/ .git
d/src/.git
d/src/.gitone

Em seguida, podemos modificar essas correspondências de várias maneiras, usando ( not ou ! ), que rejeitará o que é seguido pelo seguinte:

Não (!)

Se find d -name '.git' corresponder:

d/src/org/foo/.git
d/src/org/bar/.git
d/src/org/.git
d/src/com/.git
d/src/.git

Em seguida, find d -not -name '.git' (ou exatamente o equivalente: find d ! -name '.git' ) corresponderá a todos os outros. Não é impresso aqui, pois a lista é bem longa.

Mas selecionar apenas arquivos é curto (observe o arquivo 666 ):

$ find d -not -name '.git' -type f
d/src/org/333
d/src/org/foo/555
d/src/org/bar/444
d/src/com/222
d/src/com/.git/666
d/src/111

E -path em find d -not -path '*.git*' -type f corresponderá a apenas cinco arquivos:

$ find d -not -path '*.git*' -type f
d/src/org/333
d/src/org/foo/555
d/src/org/bar/444
d/src/com/222
d/src/111

Ameixa

Ou podemos usar -prune para "cortar" ou "remover" todos os diretórios que o anterior corresponde aceito:

$ find d \( -name '.git' -prune \) -o \( -print \)
d
d/src
d/src/org
d/src/org/three
d/src/org/333
d/src/org/.gitthree
d/src/org/foo
d/src/org/foo/five
d/src/org/foo/555
d/src/org/foo/.gitfive
d/src/org/bar
d/src/org/bar/.gitfour
d/src/org/bar/four
d/src/org/bar/444
d/src/one
d/src/com
d/src/com/.gittwo
d/src/com/222
d/src/com/two
d/src/com/ .git
d/src/111
d/src/.gitone

Deve haver algo depois de -prune. Normalmente, um '-o' e aqui também um -print para corresponder a tudo o que a remoção não correspondeu. Como você pode ver, isso combina muito mais que os cinco arquivos do seu comando. A alteração de '.git' para '*.git' removerá apenas d/src/com/ .git . Mas usar '*.git*' funcionará mais perto do grep (não exatamente o mesmo):

$ find d \( -name '*.git*' -prune \) -o \( -print \)
d
d/src
d/src/org
d/src/org/three
d/src/org/333
d/src/org/foo
d/src/org/foo/five
d/src/org/foo/555
d/src/org/bar
d/src/org/bar/four
d/src/org/bar/444
d/src/one
d/src/com
d/src/com/222
d/src/com/two
d/src/111

Isto irá corresponder apenas aos cinco arquivos (não diretórios) do seu comando:

$ find d \( -name '*.git*' -prune \) -o \( -type f -print \)

Como também isso irá corresponder apenas aos cinco arquivos iniciais. Adicionar -type f oculta muitos detalhes:

$  find d \( -name '.git' -prune \) -o \( -type f -print \)

Geralmente sem parênteses (menos claro):

$ find d -name '.git' -prune -o -type f -print

Aviso: a remoção de -print pode ter efeitos colaterais indesejados.

    
por 05.05.2016 / 00:04
0

Substituindo arquivos

Seu comando isn é confiável por vários motivos : exclui todos os caminhos que contêm .git como substring, não funciona com caminhos que contêm espaço em branco ou \'" , substitui a entrada por qualquer caractere entre com e foo . O último problema é facilmente resolvido adicionando uma barra invertida antes do ponto. Os problemas com nomes de arquivos podem ou não ser um problema em sua configuração - as árvores de origem tendem a ser inofensivas. Também -name "*" é redundante (sempre corresponde). Aqui está uma alternativa segura e simples que omite corretamente a .git subtree (s).

find . -name .git -prune -o -type f -exec sed -i 's/com\.foo/org.bar/g' {} +

Isso ainda substitui, e. com.fooalso por org.baralso , o que provavelmente não é desejável se isso acontecer. Aqui está uma construção mais segura:

find . -name .git -prune -o -type f -exec sed -i -e 's/com\.foo$/org.bar/' -e 's/com\.foo\([^-_0-9A-Za-z]\)/org.bar/' {} +

Renomeando arquivos com zsh

Carregue a função zmv com autoload -U zmv , e então você pode usá-lo para renomear arquivos com base em padrões de curingas . Eu acho que o que você está procurando é realmente

zmv -w '**/com/foo' '$1/org/bar'
zmv -w '**/*com.foo' '$1/${2}org.bar'
zmv '(**/)(*)com.foo([^-_0-9A-Za-z]*)' '$1${2}org.bar$3'
    
por 05.05.2016 / 02:54