Jak skrócić / path / to / file to / p / t / file

9

Szukam eleganckiej jednowarstwowej (np. awk), Która skróci ciąg ścieżki uniksowej, używając pierwszego znaku każdego poziomu nadrzędnego / pośredniego, ale pełnej nazwy basename. Łatwiej jest pokazać za pomocą przykładów:

  • /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
    W świetle dobrych punktów @ MichaelKjörling i @ChrisH poniżej, ten przykład pokazuje, w jaki sposób możemy pokazać pierwsze dwa znaki, gdy pierwszy znak jest kropką.
Joshua Huber
źródło
Sugestia (nie znam twojego przypadku użycia): zamiast tego skróć /f/b/.c/wizard_magic. Kropka jest często tak powszechna w danym katalogu, że stanowi bardzo małą wskazówkę, gdzie należy szukać.
Chris H
Poza tym, co powiedział @ChrisH, .zwykle oznacza po prostu „bieżący katalog”. Tak /f/b/./wizard_magicsamo jest, /f/b/wizard_magicponieważ element ścieżki ./kompresuje się do pustego elementu ścieżki.
CVn,
Dlaczego tego potrzebujesz? Czy nie możesz użyć sprytnego autouzupełniania w interaktywnej powłoce (być może zmieniając powłokę na coś odpowiedniego)
Basile Starynkevitch,

Odpowiedzi:

7

Dla tego pliku testowego:

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

Skróty można wygenerować za pomocą tego kodu 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

Edycja1: Używanie dwóch znaków dla nazw kropek

Ta wersja skraca nazwy katalogów do jednego znaku, z wyjątkiem nazw rozpoczynających się, .które są skracane do dwóch znaków:

$ 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

Jak to działa

  • -F/

    To mówi awk, aby używał ukośnika jako separatora pól na wejściu.

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

    Zapętla się ono nad każdym polem, z wyjątkiem ostatniego, i zastępuje je tylko pierwszym znakiem.

    EDYCJA 1: W poprawionej wersji określamy długość podciągu 2, gdy pole zaczyna się od ..

  • 1

    To każe awk wydrukować zmienioną linię.

  • OFS=/

    To mówi awk, aby używał ukośnika jako separatora pól na wyjściu.

John1024
źródło
Doskonała odpowiedź, niewielka modyfikacja w celu użycia separatora: awk -F/ '{for (i=1;i<NF;i++) $i=substr($i,1,1+($i~/^[.]/))(i==1||length($i)<2?"":"‥")} 1' OFS=/ <<<$PWDdaje: /foo/bar/.config/wizard_magic/f‥/b‥/.c‥/wizard_magic
ideasman42
12

Całkiem łatwe w sed (zakładając, że w nazwach plików nie ma nowych linii):

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

W awk jest to mniej łatwe, ponieważ brakuje w nim odwołań wstecznych (z wyjątkiem Gawk, ale z niezdarną składnią):

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

W zsh (ze ścieżką w $full_path):

echo "${(j:/:)${(@r:1:)${(@s:/:)${full_path:h}}}}/${full_path:t}"
Gilles „SO- przestań być zły”
źródło
2
IIRC, „referencje wsteczne” to odniesienia do grup przechwytywania występujących we wzorcu, a nie w ciągu zastępującym.
Rhymoid,
@Rhymoid \1w ciągu zastępowania ma oznaczać odniesienie do grupy przechwytywania w strukturze. Odsyłacz zwrotny to odsyłacz zwrotny bez względu na to, gdzie go używasz.
Gilles „SO- przestań być zły”
8

możesz to zrobić w następujący sposób:

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

/u/s/m/man1

a oto 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/

co jest prawie bliskie zrobieniu wszystkich tych samych czynności, które wykonuje funkcja poniżej. nie tworzy skrótów z tyldami ani nie wstawia $PWDdo głowy wiodącego nie-ukośnika, jak robi to funkcja (i w rzeczywistości nigdy nie drukuje początkowego ukośnika), ale można to później obsłużyć. przetwarza komponenty ścieżki zerowej i pojedynczych kropek oraz odsuwa ..przypadki.

podana ta sama manścieżka, jak cdpowyżej, drukuje:

u/s/m/man1

wypisze również jedną lub dwie dodatkowe kropki wiodące dla każdego komponentu ścieżki, który zaczyna się od takiego i nie jest tylko jedną lub dwiema kropkami.

poprosiłeś o zrobienie więcej niż jednego znaku dla komponentu ścieżki zaczynającego się na .. do tego doszedłem do wniosku, że każdy komponent i tak wymaga indywidualnej uwagi, a ponieważ byłem ciekawy, spróbowałem wypracować kanoniczną ścieżkę bez katalogu zmian. po kilku próbach i błędach ostatecznie zdecydowałem, że jedynym sposobem na zrobienie tego dobrze jest zrobienie tego dwa razy - wstecz i do przodu:

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

tak, że nigdy nie zmienia katalogu ani nie próbuje potwierdzić istnienia żadnego komponentu ścieżki, ale ściska powtarzające się /separatory i /./całkowicie usuwa komponenty pojedynczej kropki i odpowiednio przetwarza /../komponenty podwójnej kropki.

gdy $IFSjest ustawiony na jakiś znak inny niż biały , sekwencja dwóch lub więcej $IFSznaków spowoduje jedno lub więcej pustych pól. więc wiele kolejnych ukośników działa na argumenty o wartości zerowej. to samo dotyczy wiodącej $IFSpostaci. a więc kiedy set -- $1dzieli, jeśli wynik $1jest pusty, to zaczyna się od ukośnika, w przeciwnym ${1:+$PWD}razie , jeśli nie jest pusty, wstawiam $PWD. innymi słowy, jeśli pierwszy argument nie zaczyna się od ukośnika, zostanie $PWDdodany. jest to tak bliskie, jak w przypadku sprawdzania poprawności ścieżki .

w przeciwnym razie pierwsza forpętla rekurencyjnie odwraca kolejność składników ścieżki, takich jak:

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

... robiąc to, ignoruje komponenty jednopunktowe lub zerowe, i ..robi to ...

      1 .. 3
1     .. 3
      3
3

... drugie przejście odwraca ten efekt i jednocześnie ściska każdy składnik do 2 kropek + char lub 1-kropka + char lub char .

więc powinno dojść do kanonicznej ścieżki niezależnie od istnienia.

dodałem / odjąłem trochę do drugiej pętli. jest teraz setrzadziej (tylko raz dla każdego [!./]*komponentu) i przez casewiększość czasu ocenia wzorce zwarć (dzięki wyżej wspomnianemu wzorowi) i obejmuje ocenę dopasowania przywołania ogona ~. jeśli całość lub część wiodąca (podzielona na całe komponenty) ostatecznie kanonicznej ścieżki mogą się zgadzać ~, pasujący bit zostanie usunięty i literał ~zostanie zastąpiony. aby to zrobić, musiałem również zachować pełną kopię ścieżki obok skróconej (ponieważ dopasowanie skróconej ścieżki do ~prawdopodobnie nie byłoby bardzo pomocne) , więc jest to przechowywane $3. ostatniwhilegałąź pętli jest uruchamiana tylko wtedy, gdy ~jest dopasowana jako podzbiór $3.

jeśli uruchomisz go z set -xwłączonym śledzeniem, możesz zobaczyć, jak działa.

$ (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
źródło
4
Fajnie, ale bolą mnie oczy.
glenn jackman
1
@don_crissti - tak!
mikeserv
2

„Podejrzany” zsh motyw z Oh My zsh zawiera Perl snippet właśnie do tego, że ma wsparcie Unicode:

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

Czy chcesz mieć krótką nazwę lub używać jej w wierszu poleceń?
W przypadku wiersza polecenia mam następujące sugestie:
Czy uzupełnianie plików w powłoce nie pomaga?
Czasami masz szczęście i nie musisz robić czegoś specjalnego:

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

Gdy masz tylko niektóre katalogi, którymi jesteś zainteresowany, możesz użyć aliasów:

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"

Lub możesz ustawić zmienne dla swoich ulubionych katalogów

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

Myślę, że te opcje mają większy sens niż próba rozwiązania tego za pomocą funkcji zdefiniowanej w .bashrc (lub .profile), takich jak

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

i wywoływanie tej funkcji x ze spacjami między twoimi literami:

 # cd /path/to
 x /p t

 # cd /tmp 
 x /t

 # cd /foo/bar/.config
 x /f b 
Walter A.
źródło