Próbuję napisać funkcję powłoki bash, która pozwoli mi usunąć zduplikowane kopie katalogów ze zmiennej środowiskowej PATH.
Powiedziano mi, że można to osiągnąć za pomocą polecenia jednoliniowego za pomocą awk
polecenia, ale nie mogę wymyślić, jak to zrobić. Czy ktoś wie jak?
Odpowiedzi:
Jeśli nie masz jeszcze duplikatów
PATH
i chcesz dodać katalogi tylko wtedy, gdy jeszcze ich nie ma, możesz to zrobić z łatwością za pomocą samej powłoki.A oto fragment powłoki, który usuwa duplikaty z
$PATH
. Przegląda wpisy jeden po drugim i kopiuje te, których jeszcze nie widziałem.źródło
PATH=$PATH:x=b
x w oryginalnej PATH może mieć wartość a, a więc po iteracji w kolejności, nowa wartość zostanie zignorowana, ale w odwrotnej kolejności nowa wartość zacznie obowiązywać.PATH=x:$PATH
.PATH=$PATH:...
niePATH=...:$PATH
. Dlatego właściwsze jest iterowanie w odwrotnej kolejności. Nawet jeśli twój sposób też by działał, ludzie dołączają w odwrotny sposób.Oto zrozumiałe jedno-liniowe rozwiązanie, które robi wszystkie właściwe rzeczy: usuwa duplikaty, zachowuje porządek ścieżek i nie dodaje dwukropka na końcu. Powinien więc dać deduplikowaną ścieżkę, która daje dokładnie takie samo zachowanie jak oryginał:
Po prostu dzieli się na dwukropek (
split(/:/, $ENV{PATH})
), używa zastosowańgrep { not $seen{$_}++ }
do odfiltrowania powtarzających się wystąpień ścieżek oprócz pierwszego wystąpienia, a następnie łączy pozostałe z powrotem oddzielone dwukropkami i wypisuje wynik (print join(":", ...)
).Jeśli chcesz mieć więcej struktury wokół niego, a także możliwość deduplikacji innych zmiennych, wypróbuj ten fragment kodu, którego obecnie używam w mojej własnej konfiguracji:
Ten kod deduplikuje zarówno PATH, jak i MANPATH, i możesz łatwo wywoływać
dedup_pathvar
inne zmienne, które przechowują listy ścieżek oddzielone dwukropkami (np. PYTHONPATH).źródło
chomp
aby usunąć końcowy znak nowej linii. To zadziałało dla mnie:perl -ne 'chomp; print join(":", grep { !$seen{$_}++ } split(/:/))' <<<"$PATH"
Oto elegancki:
Dłuższy (aby zobaczyć, jak to działa):
Ok, ponieważ dopiero zaczynasz przygodę z Linuksem, oto jak ustawić PATH bez końcowego „:”
btw upewnij się, że NIE masz katalogów zawierających „:” w ŚCIEŻCE, w przeciwnym razie zostanie to pomieszane.
trochę uznania dla:
źródło
echo -n
. Twoje polecenia nie działają z „ciągami tutaj”, np. Spróbuj:awk -v RS=: -v ORS=: '!arr[$0]++' <<< ".:/foo/bin:/bar/bin:/foo/bin"
Oto jedna wkładka AWK.
gdzie:
printf %s "$PATH"
drukuje zawartość$PATH
bez końca nowej liniiRS=:
zmienia znak ogranicznika rekordu wejściowego (domyślnie jest to nowy wiersz)ORS=
zmienia separator rekordu wyjściowego na pusty ciąga
nazwa niejawnie utworzonej tablicy$0
odwołuje się do bieżącego rekordua[$0]
jest dereferencją tablicy asocjacyjnej++
jest operatorem post-increment!a[$0]++
chroni prawą stronę, tzn. upewnia się, że bieżący rekord jest drukowany tylko, jeśli nie był wcześniej drukowanyNR
aktualny numer rekordu, zaczynając od 1Oznacza to, że AWK służy do dzielenia
PATH
treści wzdłuż:
znaków ogranicznika i do filtrowania zduplikowanych wpisów bez zmiany kolejności.Ponieważ tablice asocjacyjne AWK są implementowane jako tabele skrótów, środowisko wykonawcze jest liniowe (tj. W O (n)).
Zauważ, że nie musimy szukać
:
znaków cytowanych, ponieważ powłoki nie zawierają cudzysłowów w celu obsługi katalogów z:
nazwą wPATH
zmiennej.Awk + wklej
Powyższe można uprościć za pomocą wklejania:
paste
Komenda służy do przeplatać wyjście awk z dwukropkiem. Upraszcza to akcję awk do drukowania (która jest operacją domyślną).Pyton
Taki sam jak dwu-liniowy Python:
źródło
paste
Komenda nie działa dla mnie chyba dodać końcowego znaku-
użyć standardowego wejścia.-v
bo w przeciwnym razie pojawia się błąd.-v RS=: -v ORS=
. Po prostu różne smakiawk
składni.Podobna dyskusja na ten temat tutaj .
Podchodzę trochę inaczej. Zamiast akceptować
getconf
ścieżkę ustawioną dla wszystkich różnych plików inicjujących, które są instalowane, wolę użyć do zidentyfikowania ścieżki systemowej i umieszczenia jej najpierw, następnie dodaj moją preferowaną kolejność ścieżek, a następnie użyj,awk
aby usunąć duplikaty. To może, ale nie musi, naprawdę przyspieszyć wykonywanie poleceń (i teoretycznie jest bardziej bezpieczne), ale daje mi ciepłe fuzje.źródło
:
doPATH
(tzn. Pusty ciąg znaków), ponieważ bieżący katalog roboczy jest częścią twojegoPATH
.Tak długo, jak dodajemy nie-awk oneliner:
(Może być tak proste,
PATH=$(zsh -fc 'typeset -U path; echo $PATH')
ale zsh zawsze czyta co najmniej jedenzshenv
plik konfiguracyjny, który można modyfikowaćPATH
.)Wykorzystuje dwie ładne funkcje Zsh:
typeset -T
)typeset -U
).źródło
Używa perla i ma kilka zalet:
/usr/bin:/sbin:/usr/bin
spowoduje/usr/bin:/sbin
)źródło
Również
sed
(tutaj przy użyciused
składni GNU ) może wykonać zadanie:ten działa dobrze tylko w przypadku, gdy pierwsza ścieżka jest
.
jak w przykładzie Dogbane.W ogólnym przypadku musisz dodać jeszcze jedno
s
polecenie:Działa nawet na takiej konstrukcji:
źródło
Jak inni wykazali, jest to możliwe w jednej linii za pomocą awk, sed, perl, zsh lub bash, zależy od twojej tolerancji dla długich linii i czytelności. Oto funkcja bash, która
funkcja bash
stosowanie
Aby usunąć duplikaty ze ŚCIEŻKI
źródło
To jest moja wersja:
Stosowanie:
path_no_dup "$PATH"
Przykładowe dane wyjściowe:
źródło
Najnowsze wersje bash (> = 4) również tablic asocjacyjnych, tzn. Możesz do tego użyć bash „one liner”:
gdzie:
IFS
zmienia separator pola wejściowego na:
declare -A
deklaruje tablicę asocjacyjną${a[$i]+_}
oznacza rozszerzenie parametru:_
jest podstawiane, jeśli i tylko jeślia[$i]
jest ustawione. Jest to podobne do tego,${parameter:+word}
które testuje również na wartość inną niż null. Tak więc w poniższej ocenie warunkowej wyrażenie_
(tj. Ciąg znaków składający się z jednego znaku) ma wartość true (jest to równoważne z-n _
) - podczas gdy puste wyrażenie ma wartość false.źródło
${a[$i]+_}
edytując odpowiedź i dodając jeden punkt. Reszta jest całkowicie zrozumiała, ale mnie tam zgubiłeś. Dziękuję Ci.Objaśnienie kodu awk:
Oprócz tego, że jest zwięzły, ten jednowarstwowy jest szybki: awk używa łańcuchowej tabeli skrótów, aby osiągnąć amortyzowaną wydajność O (1).
na podstawie usuwania zduplikowanych wpisów $ PATH
źródło
if ( !x[$i]++ )
. Dzięki.Użyj,
awk
aby podzielić ścieżkę:
, a następnie zapętlić każde pole i zapisać je w tablicy. Jeśli natrafisz na pole, które już znajduje się w tablicy, oznacza to, że już je widziałeś, więc nie drukuj.Oto przykład:
(Zaktualizowano, aby usunąć końcowe
:
).źródło
Rozwiązanie - nie takie eleganckie jak te, które zmieniają zmienne * RS, ale być może dość jasne:
Cały program działa w blokach BEGIN i END . Wyciąga zmienną PATH ze środowiska, dzieląc ją na jednostki. Następnie iteruje się nad wynikową tablicą p (utworzoną w kolejności według
split()
). Tablica e jest tablicą asocjacyjną, która jest używana do ustalenia, czy widzieliśmy bieżący element ścieżki (np. / Usr / local / bin ) przed, a jeśli nie, jest dołączany do np. Z logiką, aby dołączyć dwukropek do np, jeśli jest już tekst w np . END blok prostu Echos NP . Można to dodatkowo uprościć, dodając-F:
flagę, eliminując trzeci argumentsplit()
(domyślnie FS ), i zmieniającnp = np ":"
nanp = np FS
, dając nam:Naiwnie wierzyłem,
for(element in array)
że zachowałoby to porządek, ale nie działa, więc moje oryginalne rozwiązanie nie działa, ponieważ ludzie byliby zdenerwowani, gdyby ktoś nagle zakodował swoją kolejność$PATH
:źródło
Zachowywane jest tylko pierwsze wystąpienie, a porządek względny jest dobrze utrzymany.
źródło
Zrobiłbym to tylko za pomocą podstawowych narzędzi, takich jak tr, sort i uniq:
Jeśli na twojej ścieżce nie ma nic specjalnego ani dziwnego, powinno działać
źródło
sort -u
zamiastsort | uniq
.