listar diretório recursivamente, com subpath e nós de folha (arquivos) primeiro (para renomear parte de nomes de arquivos em lote)?

0

Antes de declarar isso como uma duplicata, considere que eu precisaria disso por um motivo específico: renomear em lote (ou copiar para um novo nome) de uma estrutura em árvore que contenha uma cadeia comum em nomes de arquivos e diretórios. Aqui está um exemplo (tentei no Ubuntu 14.04, então ferramentas GNU):

cd /tmp
mkdir myproj
mkdir -p myproj/myproj_AA/myproj_BB
touch myproj/myproj_AA/myproj_BB/myproj_CC.dat
mkdir myproj/myproj_AA/myproj_DD
touch myproj/myproj_AA/myproj_DD/myproj_EE.dat
mkdir -p myproj/myproj_XX/myproj_YY
touch myproj/myproj_XX/myproj_YY/myproj_ZZ.dat
mkdir -p myproj/myproj_XX/myproj_WW
touch myproj/myproj_XX/myproj_WW/myproj_QQ.dat
tree myproj # to visualise

Esta estrutura de diretórios tree se parece com isso:

myproj
├── myproj_AA
│   ├── myproj_BB
│   │   └── myproj_CC.dat
│   └── myproj_DD
│       └── myproj_EE.dat
└── myproj_XX
    ├── myproj_WW
    │   └── myproj_QQ.dat
    └── myproj_YY
        └── myproj_ZZ.dat

6 directories, 4 files

Então, eu gostaria que todas as entradas em myproj/ , incluindo myproj , fossem renomeadas para myTESTproj em vez de myproj (onde quer que isso possa ocorrer como um nome). Então, primeiro eu preciso obter uma listagem com caminhos relativos em relação ao diretório atual - e então eu preciso tê-lo classificado de tal forma que os filhos externos (eu acho que isso é equivalente a arquivos com os nomes de caminhos relativos mais longos, mas não tenho certeza) são os primeiros (porque se eu renomear / mv o diretório primeiro e, em seguida, tentar renomear um arquivo nele, ele provavelmente usará o antigo nome do dir como primeiro argumento e falhará, pois o nome agora é alterado).

Estou ciente de que há ls -R --group-directories-first myproj/ para usar ls recursivamente e diretórios de grupos primeiro, mas sua saída é assim:

$ ls -R --group-directories-first myproj/
myproj/:
myproj_AA  myproj_XX

myproj/myproj_AA:
myproj_BB  myproj_DD

myproj/myproj_AA/myproj_BB:
myproj_CC.dat

myproj/myproj_AA/myproj_DD:
myproj_EE.dat

myproj/myproj_XX:
myproj_WW  myproj_YY

myproj/myproj_XX/myproj_WW:
myproj_QQ.dat

myproj/myproj_XX/myproj_YY:
myproj_ZZ.dat

... ou seja, não é uma lista simples com subcaminhos, que eu poderia facilmente alimentar a while read f; do ...

O mais próximo que cheguei foi usar find :

$ find myproj/
myproj/
myproj/myproj_AA
myproj/myproj_AA/myproj_DD
myproj/myproj_AA/myproj_DD/myproj_EE.dat
myproj/myproj_AA/myproj_BB
myproj/myproj_AA/myproj_BB/myproj_CC.dat
myproj/myproj_XX
myproj/myproj_XX/myproj_YY
myproj/myproj_XX/myproj_YY/myproj_ZZ.dat
myproj/myproj_XX/myproj_WW
myproj/myproj_XX/myproj_WW/myproj_QQ.dat

Então, aqui eu tenho uma lista simples de subcaminhos, no entanto, ele é classificado primeiro no nó raiz em direção aos nós folha - e eu preciso dos nós folha primeiro. E estou tentando coisas como find myproj/ | sort -n , mas parece não fazer diferença. Então, se eu fizer algo como:

$ find myproj/ | sort -n | while read f; do mv -v $f $(echo $f | sed 's/myproj/myTESTproj/g'); done
‘myproj/’ -> ‘myTESTproj/’
mv: cannot stat ‘myproj/myproj_AA’: No such file or directory
mv: cannot stat ‘myproj/myproj_AA/myproj_BB’: No such file or directory
mv: cannot stat ‘myproj/myproj_AA/myproj_BB/myproj_CC.dat’: No such file or directory
...

... então a renomeação recursiva pretendida falha imediatamente, pois o nó raiz (diretório) é renomeado primeiro e, portanto, todas as outras referências a ele são inválidas.

Então, como posso obter uma listagem recursiva apropriada de um subdiretório com nós de folha primeiro, para usá-lo em uma renomeação em lote como essa?

    
por sdaau 06.12.2017 / 21:04

3 respostas

2

Se você pretende apenas renomear, não é suficiente que o conteúdo de cada diretório seja processado antes do próprio diretório, ou seja, você não precisa de todas folhas (de < em> todos os diretórios ) primeiro? find -depth faz exatamente isso.

$ mkdir -p a/b c/d
$ find -depth
./a/b
./a
./c/d
./c
.

Em seguida, você pode usar find -exec e Bash para renomear os arquivos:

$ find -depth ! -name . -name "*myproj*" -execdir bash -c '
    for f; do mv "$f" "${f/myproj/myTESTproj}" ; done' bash {} +
    
por 06.12.2017 / 21:35
1

Se você tiver a versão Perl do comando rename instalado (às vezes conhecido como prename ), isso funcionará para você

find myproj -depth -name '*myproj*' -exec rename -n 's!(.*)myproj!$1myTESTproj!' {} +

A opção -depth para find garante que os filhos em qualquer diretório sejam listados antes do próprio diretório. O sufixo + para a ação -exec permite várias inserções {} para uma única chamada do comando especificado. Com o custo de eficiência reduzida, você pode substituí-lo por \; .

Quando tiver certeza de que fará o que quiser, remova o -n ou substitua-o por -v .

    
por 06.12.2017 / 21:41
0

Eu me lembrei do que procurar depois que publiquei a questão - se os nós folha são aqueles com os nomes de caminho relativos mais longos (o que não tenho certeza se é verdade sempre, mas parece estar em o exemplo do OP, pelo menos), então, basta uma maneira de classificar uma lista de strings por comprimento de string; infelizmente sort não parece ter essa opção.

Mas, eu encontrei o link - e a partir daí, escolheu a perl solution:

$ find myproj/ | perl -e 'print sort { length($b) <=> length($a) } <>'
myproj/myproj_AA/myproj_DD/myproj_EE.dat
myproj/myproj_AA/myproj_BB/myproj_CC.dat
myproj/myproj_XX/myproj_YY/myproj_ZZ.dat
myproj/myproj_XX/myproj_WW/myproj_QQ.dat
myproj/myproj_AA/myproj_DD
myproj/myproj_AA/myproj_BB
myproj/myproj_XX/myproj_YY
myproj/myproj_XX/myproj_WW
myproj/myproj_AA
myproj/myproj_XX
myproj/

No entanto, a substituição trivial de sed 's/myproj/myTESTproj/g' também não funciona aqui:

$ find myproj/ | perl -e 'print sort { length($b) <=> length($a) } <>' \
> | while read f; do mv -v $f $(echo $f | sed 's/myproj/myTESTproj/g'); done
‘myproj/myproj_AA/myproj_DD/myproj_EE.dat’ -> ‘myTESTproj/myTESTproj_AA/myTESTproj_DD/myTESTproj_EE.dat’
mv: cannot move ‘myproj/myproj_AA/myproj_DD/myproj_EE.dat’ to ‘myTESTproj/myTESTproj_AA/myTESTproj_DD/myTESTproj_EE.dat’: No such file or directory
...

... então precisamos de um sed para substituir apenas a última correspondência em uma linha , que é sed -E 's/(.*)myproj/myTESTproj/g' :

$ find myproj/ | perl -e 'print sort { length($b) <=> length($a) } <>' \
| while read f; do mv -v $f $(echo $f | sed -E 's/(.*)myproj/myTESTproj/g'); done
‘myproj/myproj_AA/myproj_DD/myproj_EE.dat’ -> ‘myproj/myproj_AA/myproj_DD/myTESTproj_EE.dat’
‘myproj/myproj_AA/myproj_BB/myproj_CC.dat’ -> ‘myproj/myproj_AA/myproj_BB/myTESTproj_CC.dat’
‘myproj/myproj_XX/myproj_YY/myproj_ZZ.dat’ -> ‘myproj/myproj_XX/myproj_YY/myTESTproj_ZZ.dat’
‘myproj/myproj_XX/myproj_WW/myproj_QQ.dat’ -> ‘myproj/myproj_XX/myproj_WW/myTESTproj_QQ.dat’
‘myproj/myproj_AA/myproj_DD’ -> ‘myproj/myproj_AA/myTESTproj_DD’
‘myproj/myproj_AA/myproj_BB’ -> ‘myproj/myproj_AA/myTESTproj_BB’
‘myproj/myproj_XX/myproj_YY’ -> ‘myproj/myproj_XX/myTESTproj_YY’
‘myproj/myproj_XX/myproj_WW’ -> ‘myproj/myproj_XX/myTESTproj_WW’
‘myproj/myproj_AA’ -> ‘myproj/myTESTproj_AA’
‘myproj/myproj_XX’ -> ‘myproj/myTESTproj_XX’
‘myproj/’ -> ‘myTESTproj/’
$ tree myTESTproj/
myTESTproj/
├── myTESTproj_AA
│   ├── myTESTproj_BB
│   │   └── myTESTproj_CC.dat
│   └── myTESTproj_DD
│       └── myTESTproj_EE.dat
└── myTESTproj_XX
    ├── myTESTproj_WW
    │   └── myTESTproj_QQ.dat
    └── myTESTproj_YY
        └── myTESTproj_ZZ.dat

6 directories, 4 files

Eu acho que isso faz o que eu quero - mas, eu não tenho certeza se a suposição de nó de arquivo mais longo caminho == folha está sempre correta; e mesmo que seja - há uma maneira mais fácil de fazer isso?

EDIT: isso definitivamente falha em um caso de estrutura como este:

myproj/somespecdir/someotherdir/myproj_CC.dat
myproj/myproj_AA/myproj_DD/myproj_EE.dat
myproj/somespecdir/someotherdir
myproj/myproj_AA/myproj_DD
myproj/somespecdir
myproj/myproj_AA
myproj/

... ou seja, se a primeira ocorrência da substring a procurar e substituir no caminho renomeado for também a última (a única); e ocorre na lista antes de um caminho que tem várias ocorrências da substring.

    
por 06.12.2017 / 21:04

Tags