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.