Como nomear um arquivo no nível mais profundo de uma árvore de diretórios

4

Como faço para nomear um arquivo aleatório no nível mais profundo de uma árvore de diretórios usando comandos e scripts básicos do Bash?

Eu estava procurando uma maneira de fazê-lo como um one-liner e sem funções. Supondo também que eu estou começando no diretório atual e, em seguida, trabalhe em profundidade.

find , grep , sed e awk são comandos aceitáveis.

Minha tentativa atual é assim e não funciona:

find -type d | declare COUNT=-1; declare P=""; while read LINE ; do echo $LINE; declare C; C=$(echo $LINE | cut -c3- | sed "s/\//\n\//g" | grep '/' -c); echo $C; if [ $COUNT -gt $C ]; then let COUNT=$C; let P=$LINE; echo "Done"; fi; done

Isso só encontraria o diretório.

Como isso poderia ser resolvido da maneira mais simples?

    
por Robert Sundström 17.12.2011 / 03:45

8 respostas

10

É um pedido estranho!

Eu usaria find + awk para pegar um arquivo no diretório mais profundo:

bash-3.2$ deepest=$(find / -type f | awk -F'/' 'NF > depth {
>     depth = NF;
>     deepest = $0;
> }
>
> END {
>     print deepest;
> }')

Usar ${deepest} no seu comando mv é deixado como um exercício, mas as cinco linhas a seguir podem ajudá-lo:

bash-3.2$ echo "${deepest}"
/Developer/SDKs/MacOSX10.6.sdk/System/Library/Frameworks/Ruby.framework/Versions/1.8/usr/lib/ruby/gems/1.8/gems/activesupport-2.3.5/lib/active_support/vendor/tzinfo-0.3.12/tzinfo/definitions/America/Argentina/Buenos_Aires.rb

bash-3.2$ echo "${deepest%.*}"
/Developer/SDKs/MacOSX10.6.sdk/System/Library/Frameworks/Ruby.framework/Versions/1.8/usr/lib/ruby/gems/1.8/gems/activesupport-2.3.5/lib/active_support/vendor/tzinfo-0.3.12/tzinfo/definitions/America/Argentina/Buenos_Aires

bash-3.2$ echo "${deepest%/*}"
/Developer/SDKs/MacOSX10.6.sdk/System/Library/Frameworks/Ruby.framework/Versions/1.8/usr/lib/ruby/gems/1.8/gems/activesupport-2.3.5/lib/active_support/vendor/tzinfo-0.3.12/tzinfo/definitions/America/Argentina

bash-3.2$ echo "${deepest##*/}"
Buenos_Aires.rb

bash-3.2$ echo "${deepest##*.}"
rb

Após a atualização, pergunta:

find -type d [...] "This would only find the directory. [...] How could this be solved in the most simple way?".

Ao fornecer -type f para find para encontrar todos os arquivos ( f ), nem todos os diretórios ( d ).

    
por 17.12.2011 / 04:03
4

Você está perdendo alguns ajustes, inverte a comparação e precisa imprimir o resultado:

find -type d | {
  declare COUNT=-1
  declare P=""
  while read LINE
    do echo $LINE
    declare C=$(echo $LINE | cut -c3- | sed "s/\//\n\//g" | grep '/' -c)
    echo $C
    if [ $C -gt $COUNT ]; then let COUNT=$C; let P=$LINE; echo "Done"; fi
  done
  echo deepest: "$P"
}

Versão ligeiramente melhorada, com material de depuração descartado:

find -type d -links 2 | (
  declare COUNT=-1
  P=""
  while IFS= read -r LINE; do
    declare C=$(echo $LINE | tr -cd / | wc -c)
    if [ $C -gt $COUNT ]; then let COUNT=$C; P=$LINE; fi
  done
  echo deepest: "$P"
)
    
por 17.12.2011 / 19:10
3
find -type f | awk -F/ 'NF > maxdepth { maxdepth = NF; file = $0 }; END {print file}'

E parece que isso é essencialmente o mesmo que esta resposta na outra pergunta que você postou, o que há de errado com que / este?

    
por 17.12.2011 / 22:53
1

Eu iria com estes, embora eles vão falhar em nomes de arquivos contendo espaços principais. O primeiro gera apenas o nome do arquivo, o segundo inclui o caminho para esse arquivo também:

find -type f | sed 's:[^/]*/: :g' | LC_ALL=C sort | head -1 | sed 's/^ *//'

find -type f | sed 'h;s:[^/]*/: :g;G;s/\n/\t/' | LC_ALL=C sort | head -1 | sed 's/.*\t//'
    
por 17.12.2011 / 19:12
1

Com zsh:

f=(**/*(D.Oe:'REPLY=${REPLY//[^\/]}':[1])) && mv -- $f $f:h/new-name

Usamos zsh recursive globbing ( **/* ) e glob qualifiers (...) , com D para incluir arquivos de pontos, . para selecionar apenas arquivos regulares e Oe:'code' para definir uma ordem de classificação ( aqui invertido por causa da maiúscula O ) com base no valor de $REPLY após a execução do code .

Nesse code , o arquivo atual é passado como $REPLY . Nós removemos tudo, mas / caracteres em que $REPLY , e isso é o que é usado para a ordem de classificação. Então, o arquivo com mais / classifica por último, primeiro uma vez invertido e nós selecionamos aquele primeiro com [1] .

O bash com (equivalente) recente das ferramentas GNU pode ser algo como:

(
  export LC_ALL=C
  find . -type f -print0 |
    sed -z 'h;s:[^/]::g;G;s:[^/]::' |
    sort -rz |
    sed -z 's:/*::;q' |
    tr '
f=(**/*(D.Oe:'REPLY=${REPLY//[^\/]}':[1])) && mv -- $f $f:h/new-name
' '\n' )
    
por 15.02.2016 / 15:01
0
function step () {
    d=$1
    d=$((d+1))
    res=$(find -mindepth $d -type d ! -empty)
    test -n "$res" && step $d || echo $((d-1))
}

find -mindepth $(step 0) -type d ! -empty | head -n 1 

Aprofundar de forma recursiva em diretórios, contanto que eles contenham um arquivo ( ! -empty ). Da última etapa, temos que subtrair 1 e podemos usar o resultado em um segundo comando find .

    
por 17.12.2011 / 21:49
0

O script a seguir imprime os caminhos mais longos no sistema de arquivos e / ou abaixo do diretório atual, mas em vez de usar find , aproveita o banco de dados locate existente de caminhos (potencialmente muito longos) sem pesquisar / atravessando estruturas de diretório (potencialmente muito profundas). Note que ele contém diretórios para determinar a profundidade, não apenas o comprimento do caminho (mas pode ser facilmente modificado / simplificado para fazer isso).

$ cat find-longest-path.sh
#!/bin/bash
pattern=$1
locate -r "^${PWD}/${pattern}" \
   | while read f; do printf "$(tr -cd / <<< "$f" | wc -c):$f\n"; done \
   | sort -nr -t: -k1 \
   | head -5

Isso imprime os 5 caminhos mais profundos ( head -5 ) de um padrão de nome de arquivo (regexp) a partir de pwd . A saída é prefixada pela profundidade (ou seja, número de / no caminho). Para procurar apenas um nome de arquivo e / ou padrão específico, em qualquer lugar no sistema de arquivos, remova $PWD e apenas procure por locate -r /some_file$ (... etc, etc).

Por exemplo,

$ ./find-longest-path.sh 'foo.*log$'
5:/path/to/deep/path/foo-12345.log
3:/path/to/shallow/foobar.log
    
por 19.07.2013 / 05:05
0

Estou surpreso que ninguém mencionou o uso de %d .

Você pode listar todas as subpastas aninhadas e classificá-las por profundidade, usando:

find . -type d -printf '%d:%p\n' | sort -t: -k1 -g -r > /tmp/foo.txt

onde

  • . pode ser substituído pela pasta raiz;
  • -type d encontra subpastas aninhadas (substitua -type f pelos arquivos);
  • %d:%p imprime a profundidade, seguido pelo caminho;
  • -t: significa que o separador é : ;
  • -k1 significa que queremos classificar pela primeira chave / coluna;
  • -g significa que queremos classificar números;
  • -r significa que queremos que as pastas mais profundas sejam exibidas primeiro.

O resultado deste comando é enviado para o arquivo /tmp/foo.txt (substitua conforme desejado, você também pode usar mktemp se necessário).

O nível mais profundo pode ser determinado automaticamente com:

D=$(head -n 1 /tmp/foo.txt | awk -F ":" '{print $1}')

Se você quiser filtrar apenas essas pastas ou arquivos na profundidade D , use:

awk -F ":" '{ if ($1 == D) print $2 }' /tmp/foo.txt 

Finalmente, para selecionar uma linha aleatoriamente entre os resultados, canalize com | shuf -n 1 .

Colocando isso em um script random_deep_file <Folder> <Depth> :

Folder=$1

# Sort files by depth into temporary file
Temp=$(mktemp)
find "$Folder" -type f -printf '%d:%p\n' | sort -t: -k1 -g -r >| "$Temp"

# Determine deepest level if not specified
[ $# -lt 2 ] && { D=$(head -n 1 "$Temp" | awk -F ":" '{print $1}'); } || D=$2

# Select a random file at that depth
awk -F ":" "{ if (\ == $D) print \ }" "$Temp" | shuf -n 1

Note: If you are on OSX, you need to install (with Homebrew) and use gfind and gshuf instead.

    
por 09.11.2017 / 17:34

Tags