Come abbreviare / percorso / in / file in / p / t / file


9

Sto cercando un elegante one-liner (ad es. awk) Che accorcerà una stringa di un percorso Unix usando il primo carattere di ciascun livello genitore / intermedio, ma il nome di base completo. Più facile da mostrare con esempi:

  • /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
    Alla luce dei punti positivi di @ MichaelKjörling e @ChrisH di seguito, questo esempio mostra come potremmo mostrare i primi due caratteri quando il primo carattere è un punto.

Un suggerimento (non conosco il tuo caso d'uso): abbreviate invece di /f/b/.c/wizard_magic. Il punto è spesso così comune in una directory particolare da essere un indizio molto piccolo su dove dovresti cercare.
Chris H,

Oltre a ciò che ha detto @ChrisH, .normalmente significa semplicemente "directory corrente". Così /f/b/./wizard_magicè la stessa /f/b/wizard_magic, perché l'elemento percorso ./impacchi ad un elemento percorso vuoto.
un CVn

Perché ne hai bisogno? Non puoi usare un completamento automatico intelligente nella shell interattiva (forse cambiando la shell in qualcosa di adeguato)
Basile Starynkevitch,

Risposte:


7

Per questo file di test:

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

Le abbreviazioni possono essere generate con questo codice 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

Modifica1: utilizzo di due caratteri per i nomi dei punti

Questa versione abbrevia i nomi delle directory in un carattere tranne i nomi che iniziano con i .quali sono abbreviati in due caratteri:

$ 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

Come funziona

  • -F/

    Questo dice a awk di usare una barra come separatore di campo sull'input.

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

    Questo scorre su ogni campo, tranne l'ultimo, e lo sostituisce con solo il suo primo carattere.

    EDIT1: nella versione rivista, realizziamo la lunghezza della sottostringa 2 all'inizio del campo ..

  • 1

    Questo dice a awk di stampare la riga rivista.

  • OFS=/

    Questo dice a awk di usare una barra come separatore di campo in uscita.


Risposta eccellente, piccola modifica per usare il separatore: awk -F/ '{for (i=1;i<NF;i++) $i=substr($i,1,1+($i~/^[.]/))(i==1||length($i)<2?"":"‥")} 1' OFS=/ <<<$PWDdà: /foo/bar/.config/wizard_magic/f‥/b‥/.c‥/wizard_magic
ideasman42

12

Abbastanza facile in sed (supponendo che non ci siano nuove righe nei nomi dei file):

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

Meno facile in awk perché manca di backreferenze (tranne in Gawk, ma con una sintassi goffa):

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

In zsh (con il percorso in $full_path):

echo "${(j:/:)${(@r:1:)${(@s:/:)${full_path:h}}}}/${full_path:t}"

2
IIRC, "backreferences" sono riferimenti a gruppi di acquisizione che si verificano nel modello, non nella stringa di sostituzione.
Rhymoid,

@Rhymoid \1nella stringa di sostituzione non significa un riferimento ad un gruppo di cattura nel modello. Un backreference è un backreference indipendentemente da dove lo si utilizza.
Gilles 'SO- smetti di essere malvagio' il

8

puoi farlo come:

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

/u/s/m/man1

ed ecco un sed:

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

che si avvicina abbastanza a fare tutte le stesse cose che la funzione fa di seguito. non abbrevia con le tilde né inserisce la $PWDtestina per una non barra iniziale come la funzione (e in realtà non stampa mai la barra iniziale) ma che potrebbe essere gestita in seguito. elabora i componenti del percorso null, i punti singoli e elimina i ..casi.

dato lo stesso manpercorso di cui cdsopra stampa:

u/s/m/man1

stamperà anche uno o due punti iniziali extra per ciascun componente del percorso che inizia con tale e non è solo uno o due punti.

hai chiesto di fare più di un carattere per un componente del percorso che inizia con a .. per farlo ho pensato che ogni componente avrebbe comunque avuto bisogno di attenzione individuale e, dato che ero curioso, ho provato a elaborare un percorso canonico senza la directory di modifica. dopo un po 'di tentativi ed errori alla fine ho deciso che l'unico modo per farlo bene era farlo due volte - avanti e indietro:

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:--}"
}

in modo che non cambi mai la directory o cerchi di confermare l'esistenza di qualsiasi componente del percorso, ma comprime i /delimitatori ripetuti e rilascia /./completamente i componenti a punto singolo e elabora /../i componenti a punto doppio in modo appropriato.

quando $IFSè impostato su un carattere non bianco , una sequenza di due o più $IFScaratteri si tradurrà in uno o più campi null. così più barre consecutive si risolvono in argomenti a valore nullo. lo stesso vale per un $IFSpersonaggio principale . e così quando si set -- $1divide, se il risultato $1è nullo, inizia con una barra, altrimenti, ${1:+$PWD}se non è nullo, inserisco $PWD. in altre parole, se il primo argomento non inizia con una barra, verrà $PWDanteposto. questo è il più vicino possibile alla validazione del percorso .

in caso contrario, il primo forciclo inverte in modo ricorsivo l'ordine dei componenti del percorso, come:

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

... mentre lo fa ignora qualsiasi componente a punto singolo o nullo, e per ..questo ...

      1 .. 3
1     .. 3
      3
3

... il secondo passaggio inverte questo effetto e, mentre lo fa, schiaccia ogni componente a 2 punti + carattere , o 1 punto + carattere o carattere .

quindi dovrebbe seguire un percorso canonico indipendentemente dall'esistenza.

ho aggiunto / sottratto un po 'al secondo loop. ora setè meno frequente (solo una volta per ogni [!./]*componente) , e la casemaggior parte delle volte esegue la valutazione dei modelli di cortocircuito (grazie al modello sopra menzionato) e include una valutazione della corrispondenza di coda chiamata contro ~. se tutto o una parte iniziale (come divisa su interi componenti) del percorso finalmente canonico può corrispondere ~, il bit corrispondente verrà rimosso e ~verrà sostituito un valore letterale . per fare questo, ho dovuto mantenere una copia completa del percorso insieme anche all'abbreviazione (perché abbinare il percorso abbreviato a ~probabilmente non sarebbe molto utile) , e quindi questo è mantenuto $3. l'ultimowhileil ramo del ciclo viene eseguito solo se ~corrisponde a un sottoinsieme di $3.

se lo esegui con la set -xtraccia abilitata, puoi vederlo funzionare.

$ (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

4
Fresco, ma mi fanno male gli occhi.
Glenn Jackman,

1
@don_crissti - sì!
Mikeserv,

2

Il tema "fishy" di Zsh di Oh My Zsh contiene uno snippet Perl per fare proprio quello che ha il supporto Unicode:

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

1

Vuoi avere un nome breve o usarlo per la tua riga di comando?
Per la riga di comando ho i seguenti suggerimenti:
Il completamento del file nella shell non ti aiuta?
A volte sei fortunato e non devi fare qualcosa di speciale:

# /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 hai solo alcune directory che ti interessano, puoi usare gli alias:

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"

Oppure puoi impostare variabili per le tue directory preferite

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

Penso che queste opzioni abbiano più senso che cercare di risolverlo con una funzione definita in .bashrc (o .profile) come

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

e chiamando questa funzione x con spazi tra le tue lettere:

 # cd /path/to
 x /p t

 # cd /tmp 
 x /t

 # cd /foo/bar/.config
 x /f b 
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.