Antes de declarar isso uma duplicata, considere que eu precisaria disso por um motivo específico: renomear em lote (ou copiar para um novo nome) de uma estrutura de árvore que contém uma string comum em nomes de arquivos e diretórios. Aqui está um exemplo (experimentado 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 se tree
parece com isto:
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
Portanto, gostaria que todas as entradas em myproj/
, incluindo myproj
a própria, fossem renomeadas em myTESTproj
vez de myproj
(onde quer que ocorra como um nome). Então, primeiro preciso obter uma listagem com caminhos relativos em relação ao diretório atual - e então preciso classificá-lo de forma que os filhos mais externos (acho que isso é equivalente a arquivos com os nomes de caminho 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, provavelmente usará o antigo nome do diretório como primeiro argumento e falhará, pois o nome agora foi alterado).
Estou ciente de que é ls -R --group-directories-first myproj/
necessário usar ls
recursivamente e agrupar diretórios 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 alimentarwhile read f; do ...
O mais próximo que cheguei é 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, é classificado primeiro o nó raiz em direção aos nós folha - e eu preciso primeiro dos nós folha. 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 adequada de um subdiretório com nós de folha primeiro, para usá-lo em uma renomeação em lote como esta?
Se você pretende apenas renomear, não basta que o conteúdo de cada diretório seja processado antes do próprio diretório, ou seja, não precisa de todas as folhas (de todos os diretórios) primeiro?
find -depth
faz exatamente isso.Então você pode usar
find -exec
e Bash para renomear os arquivos:Se você tiver a versão Perl do
rename
comando instalada (às vezes conhecida comoprename
), isso funcionará para vocêA
-depth
opção parafind
garante que os filhos em qualquer diretório sejam listados antes do próprio diretório. O+
sufixo para a-exec
ação permite várias{}
inserções para uma única chamada do comando especificado. Ao custo de eficiência reduzida, você pode substituí-lo por\;
.Quando tiver certeza de que fará o que você deseja, remova
-n
ou substitua por-v
.Lembrei-me do que procurar depois de postar a pergunta - se os nós de folha são aqueles com os nomes de caminho relativos mais longos (o que não tenho certeza se é sempre verdade, mas parece estar pelo menos no exemplo OP), então um simplesmente precisa de uma maneira de classificar uma lista de strings por comprimento de string; infelizmente
sort
não parece ter essa opção.Mas, encontrei https://stackoverflow.com/questions/5917576/sort-a-text-file-by-line-length-posing-spaces - e a partir daí, escolhi a
perl
solução:No entanto, a substituição trivial
sed 's/myproj/myTESTproj/g'
também não funciona aqui:... então precisamos
sed
substituir apenas a última correspondência em uma linha , que ésed -E 's/(.*)myproj/\1myTESTproj/g'
:Acho que isso faz o que eu quero - mas não tenho certeza se a suposição de nome de caminho mais longo == nó de arquivo de folha está sempre correta; e mesmo que seja - existe uma maneira mais fácil de fazer isso?
EDIT: isso definitivamente falha em um caso de estrutura como esta:
... ou seja, se a primeira ocorrência da substring a ser procurada e substituída no caminho renomeado também for a última (a única); e ocorre na lista antes de um caminho que possui várias ocorrências da substring.