Abkürzung von / path / to / file to / p / t / file

9

Ich suche einen eleganten Einzeiler (z. B. awk), der eine Zeichenfolge eines Unix-Pfads mit dem ersten Zeichen jeder übergeordneten / mittleren Ebene, aber dem vollständigen Basisnamen verkürzt. Einfacher anhand von Beispielen zu zeigen:

  • /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
    In Anbetracht der guten Punkte von @ MichaelKjörling und @ChrisH unten zeigt dieses Beispiel, wie wir die ersten beiden Zeichen anzeigen können, wenn das erste Zeichen ein Punkt ist.
Joshua Huber
quelle
Ein Vorschlag (ich kenne Ihren Anwendungsfall nicht): stattdessen abkürzen auf /f/b/.c/wizard_magic. Der Punkt ist in einem bestimmten Verzeichnis häufig so häufig, dass er nur einen sehr kleinen Hinweis darauf gibt, wo Sie suchen sollten.
Chris H
Abgesehen von dem, was @ChrisH gesagt hat, .bedeutet dies normalerweise nur "aktuelles Verzeichnis". Dies /f/b/./wizard_magicist dasselbe wie, /f/b/wizard_magicweil das ./Pfadelement zu einem leeren Pfadelement komprimiert wird.
ein CVn
Warum brauchst du das? Können Sie nicht eine clevere Autovervollständigung in Ihrer interaktiven Shell verwenden (vielleicht ändern Sie Ihre Shell auf etwas
Angemessenes

Antworten:

7

Für diese Testdatei:

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

Die Abkürzungen können mit diesem awk-Code generiert werden:

$ 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: Verwenden von zwei Zeichen für Punktnamen

In dieser Version werden Verzeichnisnamen auf ein Zeichen .abgekürzt, mit Ausnahme von Namen, die mit zwei Zeichen abgekürzt werden:

$ 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

Wie es funktioniert

  • -F/

    Dies weist awk an, bei der Eingabe einen Schrägstrich als Feldtrennzeichen zu verwenden.

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

    Dies durchläuft jedes Feld mit Ausnahme des letzten und ersetzt es durch nur das erste Zeichen.

    EDIT1: In der überarbeiteten Version geben wir die Länge des Teilstrings 2 an, wenn das Feld mit beginnt ..

  • 1

    Dies weist awk an, die überarbeitete Zeile zu drucken.

  • OFS=/

    Dies weist awk an, bei der Ausgabe einen Schrägstrich als Feldtrennzeichen zu verwenden.

John1024
quelle
Hervorragende Antwort, geringfügige Änderung zur Verwendung des Trennzeichens: awk -F/ '{for (i=1;i<NF;i++) $i=substr($i,1,1+($i~/^[.]/))(i==1||length($i)<2?"":"‥")} 1' OFS=/ <<<$PWDgibt: /foo/bar/.config/wizard_magic/f‥/b‥/.c‥/wizard_magic
ideasman42
12

Ziemlich einfach in sed (vorausgesetzt, die Dateinamen enthalten keine Zeilenumbrüche):

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

In awk weniger einfach, weil es keine Rückreferenzen gibt (außer in Gawk, aber mit einer ungeschickten Syntax):

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

In zsh (mit dem Pfad in $full_path):

echo "${(j:/:)${(@r:1:)${(@s:/:)${full_path:h}}}}/${full_path:t}"
Gilles 'SO - hör auf böse zu sein'
quelle
2
IIRC, "Rückreferenzen" sind Verweise auf Erfassungsgruppen, die im Muster und nicht in der Ersatzzeichenfolge vorkommen.
Rhymoid
@Rhymoid \1im Ersetzungsstring hat einen Verweis auf eine Capture - Gruppe im Muster bedeuten. Eine Rückreferenz ist eine Rückreferenz, unabhängig davon, wo Sie sie verwenden.
Gilles 'SO - hör auf böse zu sein'
8

Sie können es tun wie:

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

/u/s/m/man1

und hier ist ein 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/

das kommt ziemlich nahe daran, all die gleichen Dinge zu tun, die die Funktion unten macht. Es wird nicht mit Tildes abgekürzt oder $PWDfür einen führenden Nicht-Schrägstrich in den Kopf eingefügt, wie dies bei der Funktion der Fall ist (und tatsächlich wird der führende Schrägstrich nie gedruckt), aber das könnte später behandelt werden. Es verarbeitet Nullpfadkomponenten und Einzelpunkte und beseitigt ..Fälle.

Bei gleichem manPfad wie cdoben wird Folgendes gedruckt:

u/s/m/man1

Außerdem werden ein oder zwei zusätzliche führende Punkte für jede Pfadkomponente gedruckt, die mit einer solchen beginnt und nicht nur aus einem oder zwei Punkten besteht.

Sie haben gefragt, ob Sie mehr als das eine Zeichen für eine Pfadkomponente tun möchten, beginnend mit a .. Um dies zu tun, dachte ich, dass jede Komponente sowieso individuelle Aufmerksamkeit benötigen würde, und weil ich neugierig war, versuchte ich mich daran, einen kanonischen Pfad ohne das Änderungsverzeichnis auszuarbeiten. Nach einigem Ausprobieren entschied ich schließlich, dass der einzige Weg, es richtig zu machen, darin bestand, es zweimal zu tun - vorwärts und rückwärts:

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

Damit wird niemals das Verzeichnis geändert oder versucht, die Existenz einer Pfadkomponente zu bestätigen, sondern es werden wiederholte /Trennzeichen gequetscht und /./Einzelpunktkomponenten vollständig gelöscht und /../Doppelpunktkomponenten entsprechend verarbeitet.

Wenn $IFSein Nicht-Leerzeichen festgelegt ist , führt eine Folge von zwei oder mehr $IFSZeichen zu einem oder mehreren Nullfeldern. Daher werden mehrere aufeinanderfolgende Schrägstriche zu Argumenten mit Nullwerten verarbeitet. Gleiches gilt für eine $IFSHauptfigur. und so, wenn geteilt wird set -- $1, wenn das Ergebnis $1null ist, dann begann es mit einem Schrägstrich, sonst, ${1:+$PWD}wenn es nicht null ist, dann füge ich ein $PWD. Mit anderen Worten, wenn das erste Argument nicht mit einem Schrägstrich beginnt, wird es $PWDvorangestellt. Das ist so nah wie die Pfadvalidierung .

Andernfalls forinvertiert die erste Schleife rekursiv die Reihenfolge der Pfadkomponenten wie:

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

... dabei werden Einzelpunkt- oder Nullkomponenten ignoriert, und ..das tut es auch ...

      1 .. 3
1     .. 3
      3
3

... der zweite Durchgang kehrt diesen Effekt um und drückt dabei jede Komponente entweder auf 2 Punkte + Zeichen oder 1 Punkt + Zeichen oder Zeichen .

es sollte also unabhängig von der Existenz zu einem kanonischen Weg führen.

Ich habe ein wenig zur zweiten Schleife addiert / subtrahiert. Jetzt sets weniger oft (nur einmal für jede [!./]*Komponente) und Kurzschlüsse caseMuster Auswertungen der meiste Zeit (dank das oben genannte Muster) und enthält eine Tail-Call - Match-Auswertung gegen ~. Wenn alle oder ein führender Teil (wie auf ganze Komponenten aufgeteilt) des endgültig kanonischen Pfades übereinstimmen können ~, wird das Übereinstimmungsbit entfernt und ein Literal ~ersetzt. Um dies zu tun, musste ich neben dem abgekürzten Pfad auch eine vollständige Kopie des Pfads pflegen (da es ~wahrscheinlich nicht sehr hilfreich wäre, den abgekürzten Pfad mit zu verknüpfen) , und dies wird beibehalten $3. das LetztewhileDer Schleifenzweig wird nur ausgeführt, wenn er ~als Teilmenge von übereinstimmt $3.

Wenn Sie es mit set -xaktiviertem Trace ausführen, können Sie beobachten, wie es funktioniert.

$ (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
mikeserv
quelle
4
Cool, aber meine Augen tun weh.
Glenn Jackman
1
@don_crissti - ja!
Mikeserv
2

Das "fischige" Zsh-Thema von Oh My Zsh enthält ein Perl-Snippet, das genau das tut, was Unicode-Unterstützung bietet:

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

Möchten Sie einen Kurznamen haben oder ihn für Ihre Befehlszeile verwenden?
Für die Befehlszeile habe ich folgende Vorschläge:
Hilft Ihnen die Dateivervollständigung in Ihrer Shell nicht?
Manchmal hat man Glück und muss nichts Besonderes tun:

# /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

Wenn Sie nur einige Verzeichnisse haben, an denen Sie interessiert sind, können Sie Aliase verwenden:

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"

Oder Sie können Variablen für Ihre Lieblingsverzeichnisse einrichten

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

Ich denke, diese Optionen sind sinnvoller, als zu versuchen, dies mit einer in .bashrc (oder .profile) definierten Funktion wie zu lösen

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

und Aufruf dieser Funktion x mit Leerzeichen zwischen Ihren Buchstaben:

 # cd /path/to
 x /p t

 # cd /tmp 
 x /t

 # cd /foo/bar/.config
 x /f b 
Walter A.
quelle