Como abreviar / path / to / file para / p / t / file

9

Estou à procura de um elegante one-liner (por exemplo, awk ) que encurte uma string de um caminho Unix usando o primeiro caractere de cada nível pai / intermediário, mas o nome completo da base. Mais fácil de mostrar por exemplos:

  • /path/to/file/p/t/file
  • /tmp/tmp
  • /foo/bar/.config/wizard_magic/f/b/./wizard_magic
  • /foo/bar/.config/wizard_magic/f/b/.c/wizard_magic
    Considerando os pontos positivos de @ MichaelKjörling e @ChrisH abaixo, este exemplo mostra como podemos mostrar os dois primeiros caracteres quando o primeiro caractere é um ponto.
por Joshua Huber 03.12.2015 / 00:47

5 respostas

7

Para este arquivo de teste:

$ cat path
/path/to/file
/tmp
/foo/bar/.config/wizard_magic

As abreviaturas podem ser geradas com este código awk:

$ awk -F/ '{for (i=1;i<NF;i++) $i=substr($i,1,1)} 1' OFS=/ path
/p/t/file
/tmp
/f/b/./wizard_magic

Edit1: Usando dois caracteres para nomes de pontos

Esta versão abrevia nomes de diretórios para um caractere, exceto para nomes que começam com . , que são abreviados para dois caracteres:

$ awk -F/ '{for (i=1;i<NF;i++) $i=substr($i,1,1+($i~/^[.]/))} 1' OFS=/ path
/p/t/file
/tmp
/f/b/.c/wizard_magic

Como funciona

  • -F/

    Isto diz ao awk para usar uma barra como o separador de campo na entrada.

  • for (i=1;i<NF;i++) $i=substr($i,1,1)

    Isso faz um loop sobre cada campo, exceto o último, e o substitui apenas pelo primeiro caractere.

    EDIT1: Na versão revisada, fazemos o comprimento da substring 2 quando o campo começa com . .

  • 1

    Isso diz ao awk para imprimir a linha revisada.

  • OFS=/

    Isto diz ao awk para usar uma barra como o separador de campos na saída.

por 03.12.2015 / 01:09
12

Bastante fácil no sed (supondo que não haja novas linhas nos nomes dos arquivos):

sed 's!\([^/]\)[^/]*/!/!g'

Menos fácil no awk porque não tem referências anteriores (exceto no Gawk, mas com uma sintaxe desajeitada):

awk -v FS=/ -v OFS=/ '{for (i=1; i<NF; i++) $i=substr($i,1,1)} 1'

Em zsh (com o caminho em $full_path ):

echo "${(j:/:)${(@r:1:)${(@s:/:)${full_path:h}}}}/${full_path:t}"
    
por 03.12.2015 / 01:11
8

você pode fazer assim:

cd /usr///.//share/../share//man/man1 || exit
IFS=/; set -f
printf %.1s/  ${PWD%/*}
printf %s\n "${PWD##*/}"
/u/s/m/man1

e aqui está um sed :

printf %s "$file" |
tr /\n \n/      | sed -et$ \
    -e '\|^\.\.$|{x;s|\(.*\)\n.*$||;x;}'  \
    -e 's|^\.\{0,2\}$||;\|.|H;$!d;x'        \
-e$ -e '\|\(\.\{0,2\}.\)\(.*\)\(\n\)|!b'    \
    -e 's|||;P;s|\n||;D' |
tr /\n \n/

que chega perto de fazer as mesmas coisas que a função faz abaixo. ele não abrevia com tils ou insere o $PWD na cabeça para uma não-barra líder como a função faz (e, na verdade, nunca imprime a barra inicial) mas que poderia ser manipulada posteriormente . ele processa componentes de caminho nulo e pontos únicos e elimina .. casos.

considerando o mesmo caminho man , como cd acima, imprime:

u/s/m/man1

ele também imprime um ou dois pontos iniciais extras para cada componente do caminho que começa com tal e não é apenas um ou dois pontos.

você perguntou sobre fazer mais do que um caractere para um componente de caminho que começa com . . para fazer isso eu percebi que cada componente precisaria de atenção individual de qualquer maneira, e porque eu estava curioso, eu tentei a minha mão trabalhando em um caminho canônico sem o diretório de mudança. Depois de algumas tentativas e erros, decidi que a única maneira de fazer o certo era fazê-lo duas vezes - para trás e para frente:

pathbytes(){
    local IFS=/   o="$-" p
    set -f${ZSH_VERSION+LFy}
    set -- ${1:-$PWD}
    for p   in      /${1:+$PWD} $*
    do      case    $p in   (.|"")  ;;
            (..)    ${1+shift}      ;;
            (/)     set --          ;;
            (*)     set -- $p $*;   esac
    done
    for p   in      //$* ""
    do      case   ${p:-/$3}        in
            ([!./]*)                ;;
            (..*)   set "..$@"      ;;
            (.*)    set ".$@"       ;;
            (//*) ! set "" $1 $1    ;;
            (~)   ! p=\~            ;;
            (~/*)   p="~/$2";set $HOME
                  ! while "${2+shift}" 2>&3
                    do   p="~/${p#??*/}"
                    done 3>/dev/null;;
            esac&&  set ""  "${p%"${p#$1?}"}/$2" "$p/$3"
    done;   printf %s\n "${p:-$2}"
    set +f  "-${o:--}"
}

para que nunca mude o diretório ou tente confirmar a existência de qualquer componente de caminho, mas ele comprime os componentes / e gotas /./ single-dot repetidos e processa os componentes /../ double-dot apropriadamente.

quando $IFS é definido para algum caractere não espaço em branco , uma sequência de dois ou mais caracteres $IFS resultará em um ou mais campos nulos. Assim, várias barras consecutivas funcionam com argumentos de valor nulo. o mesmo é verdadeiro para um caractere $IFS inicial. e assim quando set -- $1 divide, se o $1 resultante for nulo, então começou com uma barra, senão, ${1:+$PWD} se não for nulo, então eu insiro $PWD . Em outras palavras, se o primeiro argumento não iniciar com uma barra, ele receberá $PWD prefixado. isso é o mais próximo que isso chega ao caminho validação .

caso contrário, o primeiro loop for recursivamente inverte a ordem dos componentes do caminho, como:

      1 2 3
1     2 3
2 1   3
3 2 1

... enquanto faz isso, ele ignora qualquer componente de ponto único ou nulo, e para .. ele faz ...

      1 .. 3
1     .. 3
      3
3

... o segundo passo reverte esse efeito e, ao fazer isso, ele pressiona cada componente para 2-pontos + char , ou 1-ponto + char , ou char .

por isso deve funcionar para um caminho canônico, independentemente da existência.

eu adicionei / subtrai um pouco ao segundo loop. agora set s menos frequentemente (apenas uma vez para cada [!./]* componente) , e curtos circuitos case avaliações de padrões na maioria das vezes (graças ao padrão mencionado acima) , e inclui uma avaliação de correspondência da última chamada em relação a ~ . se all ou uma parte inicial (dividida em componentes inteiros) do caminho canônico final puder corresponder a ~ , o bit correspondente será removido e um literal ~ será substituído. A fim de fazer isso, eu tive que manter uma cópia completa do caminho ao lado do abreviado, bem (porque a correspondência do caminho abreviado para ~ provavelmente não seria muito útil) , e assim é mantido em $3 . a última ramificação de loop while só será executada se ~ for correspondido como um subconjunto de $3 .

se você executá-lo com o set -x trace ativado, poderá assisti-lo funcionar.

$ (set -x;pathbytes ..abc/def/123///././//.././../.xzy/mno)
+ pathbytes ..abc/def/123///././//.././../.xzy/mno
+ local IFS=/ o=xsmi p
+ set -f
+ set -- ..abc def 123   . .   .. . .. .xzy mno
+ set --
+ set -- home
+ set -- mikeserv home
+ set -- ..abc mikeserv home
+ set -- def ..abc mikeserv home
+ set -- 123 def ..abc mikeserv home
+ shift
+ shift
+ set -- .xzy ..abc mikeserv home
+ set -- mno .xzy ..abc mikeserv home
+ set  mno mno
+ set . mno mno
+ set  .x/mno .xzy/mno
+ set .. .x/mno .xzy/mno
+ set  ..a/.x/mno ..abc/.xzy/mno
+ set  m/..a/.x/mno mikeserv/..abc/.xzy/mno
+ set  h/m/..a/.x/mno home/mikeserv/..abc/.xzy/mno
+ p=~/h/m/..a/.x/mno
+ set  home mikeserv
+ shift
+ p=~/m/..a/.x/mno
+ shift
+ p=~/..a/.x/mno
+
+ printf %s\n ~/..a/.x/mno
~/..a/.x/mno
+ set +f -xsmi
    
por 03.12.2015 / 01:10
2

O tema zsh "fishy" do Oh My Zsh contém um snippet Perl para fazer exatamente isso que tem suporte a Unicode:

perl -pe '
   BEGIN {
      binmode STDIN,  ":encoding(UTF-8)";
      binmode STDOUT, ":encoding(UTF-8)";
   }; s|^$HOME|~|g; s|/([^/.])[^/]*(?=/)|/$1|g; s|/\.([^/])[^/]*(?=/)|/.$1|g;
'
    
por 03.12.2015 / 20:58
1

Você quer ter um nome curto ou usá-lo para sua linha de comando?
Para a linha de comando, tenho as seguintes sugestões:
A conclusão do arquivo no seu shell não ajuda você?
Às vezes você tem sorte e não precisa fazer algo especial:

# /path/to/file -> /p/t/file
ls -l /*/*/file 

# /tmp -> /tmp
cd /tmp

# /foo/bar/.config/wizard_magic -> /f/b/./wizard_magic
ls -l /*/*/*/wizard_magic -> /f/b/./wizard_magic

Quando você tem apenas alguns diretórios nos quais está interessado, é possível usar aliases:

alias cdto="cd /path/to"
alias cdtmp="cd /tmp"
alias cdcfg="cd /foo/bar/.config"
alias cddeep="cd /home/john/workdir/project1/version3/maven/x/y/z/and/more"

Ou você pode configurar variáveis para seus diretórios favoritos

export p="/path/to"
export f="/foo/bar/.config"
ls -l $p/file
ls -l $f/wizard_magic

Acho que essas opções fazem mais sentido do que tentar resolver isso com uma função definida em .bashrc (ou .profile) como

function x { 
   xxpath=""
   while [ $# -ne 0 ]; do
     xxpath+="${1}*/"
     shift
   done
   cd $(echo "${xxpath}")
}

e chamando esta função x com espaços entre suas letras:

 # cd /path/to
 x /p t

 # cd /tmp 
 x /t

 # cd /foo/bar/.config
 x /f b 
    
por 04.12.2015 / 23:32

Tags