Jak mogę czysto dodać do $ PATH?


31

Chciałbym znaleźć sposób dodania rzeczy do $ PATH, dla całego systemu lub dla pojedynczego użytkownika, bez potencjalnego wielokrotnego dodawania tej samej ścieżki.

Jednym z powodów, dla których warto to zrobić, jest dodanie dodatków .bashrc, które nie wymagają logowania, a także są bardziej przydatne w systemach, które używają (np.) lightdm, Które nigdy nie wywołuje .profile.

Zdaję sobie sprawę z pytań dotyczących usuwania duplikatów z $ PATH, ale nie chcę ich usuwać . Chciałbym sposób dodawać ścieżki tylko wtedy, gdy nie są one już obecne.



goldi, nie wiem dlaczego, ale widziałem twój pierwszy komentarz nawet z pustymi. Ale tak, prefiksy nazw również działają, bez obaw! Zamykanie w drugą stronę również jest w porządku.
Ciro Santilli 新疆 改造 中心 法轮功 六四 事件

Okej, dopóki dostaniesz moją wiadomość. Czasami takie odwrócenie powoduje nieco chaosu, myślę, że zobaczymy, co się stanie.
goldilocks,

Odpowiedzi:


35

Załóżmy, że nowa ścieżka, którą chcemy dodać, to:

new=/opt/bin

Następnie za pomocą dowolnej powłoki POSIX możemy sprawdzić, czy newjest już na ścieżce i dodać ją, jeśli nie jest:

case ":${PATH:=$new}:" in
    *:"$new":*)  ;;
    *) PATH="$new:$PATH"  ;;
esac

Zwróć uwagę na użycie dwukropków. Bez dwukropków moglibyśmy pomyśleć, że powiedzmy, że new=/binbył już na ścieżce, ponieważ wzór pasował do siebie /usr/bin. Chociaż ŚCIEŻKI zwykle zawierają wiele elementów, obsługiwane są również specjalne przypadki zer i jeden element ŚCIEŻKI. Sprawa ŚCIEŻKI początkowo nieposiadająca elementów (pusta) jest obsługiwana przez użycie ${PATH:=$new}przypisania PATHdo, $newjeśli jest pusta. Ustawienie wartości domyślnych parametrów w ten sposób jest cechą wszystkich powłok POSIX: patrz sekcja 2.6.2 dokumentacji POSIX .)

Funkcja wywoływalna

Dla wygody powyższy kod można wprowadzić w funkcję. Tę funkcję można zdefiniować w wierszu poleceń lub, aby była dostępna na stałe, umieścić w skrypcie inicjującym powłokę (dla użytkowników bash ~/.bashrc):

pupdate() { case ":${PATH:=$1}:" in *:"$1":*) ;; *) PATH="$1:$PATH" ;; esac; }

Aby użyć tej funkcji aktualizacji ścieżki, aby dodać katalog do bieżącej ŚCIEŻKI:

pupdate /new/path

@hammar OK. Dodałem do tego skrzynkę.
John1024

1
Możesz zapisać 2 rozróżnienia wielkości liter - por. unix.stackexchange.com/a/40973/1131 .
maxschlepzig

3
Jeśli PATHjest pusty, doda to pusty wpis (tj. Bieżący katalog) do PATH. Myślę, że potrzebujesz innej skrzynki.
CB Bailey,

2
@CharlesBailey Nie inny case. Po prostu zrób case "${PATH:=$new}". Zobacz moją odpowiedź na podobne awarie.
mikeserv

1
@ mc0e Dodałem przykład użycia funkcji powłoki, aby ukryć „szum linii”.
John1024

9

Utwórz plik o /etc/profile.dnazwie np. mypath.sh(Lub cokolwiek chcesz). Jeśli używasz lightdm, upewnij się, że jest to wykonalne, w przeciwnym razie użyj /etc/bashrclub plik pochodzący z tego samego. Dodaj do tego następujące funkcje:

checkPath () {
        case ":$PATH:" in
                *":$1:"*) return 1
                        ;;
        esac
        return 0;
}

# Prepend to $PATH
prependToPath () {
        for a; do
                checkPath $a
                if [ $? -eq 0 ]; then
                        PATH=$a:$PATH
                fi
        done
        export PATH
}

# Append to $PATH
appendToPath () {
        for a; do
                checkPath $a
                if [ $? -eq 0 ]; then
                        PATH=$PATH:$a
                fi
        done
        export PATH
}

Rzeczy na początku (poprzedzone) $ PATH mają pierwszeństwo przed tym, co następuje, i odwrotnie, rzeczy na końcu (dołączone) zostaną zastąpione przez to, co nastąpi wcześniej. Oznacza to, że jeśli $ PATH jest /usr/local/bin:/usr/bini istnieje plik wykonywalny gotchaw obu katalogach, /usr/local/bindomyślnie zostanie użyty ten w.

Możesz teraz - w tym samym pliku, w innym pliku konfiguracyjnym powłoki lub z wiersza poleceń - użyć:

appendToPath /some/path /another/path
prependToPath /some/path /yet/another/path

Jeśli jest to w .bashrc, zapobiegnie to pojawianiu się wartości więcej niż raz podczas uruchamiania nowej powłoki. Istnieje ograniczenie polegające na tym, że jeśli chcesz dołączyć coś, co zostało wcześniej przygotowane (tj. Przesunąć ścieżkę w obrębie $ PATH) lub odwrotnie, musisz to zrobić sam.


rozdzielenia programów $PATHz IFS=:ostatecznie jest bardziej elastyczny niż case.
mikeserv

@mikeserv Bez wątpienia. Jest to rodzaj hackowania dla caseIMO. Wyobrażam sobie, awkże tutaj też można by dobrze wykorzystać.
złotowłosa

Trafne spostrzeżenie. I, jak myślę, gawkmożna bezpośrednio przypisać $PATH.
mikeserv

5

Możesz to zrobić w ten sposób:

echo $PATH | grep /my/bin >/dev/null || PATH=$PATH:/my/bin

Uwaga: jeśli budujesz PATH z innych zmiennych, sprawdź, czy nie są one puste, ponieważ wiele powłok interpretuje „„ jak ”. .


+1 Według strony podręcznika -qPOSIX jest wymagany przez grep, ale nie wiem, czy to oznacza, że ​​nadal istnieją niektóre (nie POSIX) grep, które go nie mają.
złotowłosa

1
zauważ, że wzór grep jest zbyt szeroki. Rozważ użycie egrep -q "(^ |:) / my / bin (: | \ $)" zamiast grep / my / bin> / dev / null. Dzięki tej modyfikacji twoje rozwiązanie jest poprawne i myślę, że jest to bardziej czytelne rozwiązanie niż obecnie preferowana odpowiedź z @ john1024. Zauważ, że użyłem podwójnych cudzysłowów, więc zamiast/my/bin
mc0e

5

Ważną częścią kodu jest sprawdzenie, czy PATH zawiera określoną ścieżkę:

printf '%s' ":${PATH}:" | grep -Fq ":${my_path}:"

To znaczy, upewnij się, że każda ścieżka wewnątrz PATHjest oddzielona po obu stronach PATHseparatorem ( :), a następnie sprawdź ( -q), czy istnieje literalny ciąg ( -F) składający się z PATHseparatora, twojej ścieżki i innego PATHseparatora. Jeśli nie, możesz bezpiecznie dodać ścieżkę:

if ! printf '%s' ":${PATH-}:" | grep -Fq ":${my_path-}:"
then
    PATH="${PATH-}:${my_path-}"
fi

Powinno to być zgodne z POSIX i powinno działać z każdą ścieżką niezawierającą znaku nowej linii. Jest bardziej skomplikowany, jeśli chcesz, aby działał ze ścieżkami zawierającymi znak nowej linii, a jednocześnie jest zgodny z POSIX, ale jeśli masz grepobsługę -z, możesz z niego korzystać.


4

Od lat noszę tę małą funkcję w różnych ~/.profileplikach. Myślę , że został napisany przez sysadmina w laboratorium, w którym kiedyś pracowałem, ale nie jestem pewien. W każdym razie jest podobny do podejścia Goldilocka, ale nieco inny:

pathmunge () {
        if ! echo $PATH | /bin/grep -Eq "(^|:)$1($|:)" ; then
           if [ "$2" = "after" ] ; then
              PATH=$PATH:$1
           else
              PATH=$1:$PATH
           fi
        fi
}

Aby dodać nowy katalog na początku PATH:

pathmunge /new/path

i do końca:

pathmunge /new/path after

To działa dla mnie! Ale zamieniłem na logikę, aby wstawić ją domyślnie po i zastąpić „przed”. :)
Kevin Pauli,

pathmunge jest częścią dystrybucji linux centos / etc / profile, ma parametr przed i po. Nie widzę tego w moim najnowszym ubuntu 16.
Kemin Zhou

Wygląda na to, że działa poprawnie na macOS 10.12 po /bin/grep->grep
Ben Creasy

4

AKTUALIZACJA:

Zauważyłem, że twoja własna odpowiedź ma osobną funkcję do dodawania lub dodawania do $PATH. Podobał mi się ten pomysł. Dodałem więc trochę obsługi argumentów. Poprawnie _ją też umieściłem:

_path_assign() { oFS=$IFS ; IFS=: ; add=$* ; unset P A ; A=
    set -- ${PATH:=$1} ; for p in $add ; do {
        [ -z "${p%-[AP]}" ] && { unset P A
                eval ${p#-}= ; continue ; }
        for d ; do [ -z "${d%"$p"}" ] && break
        done ; } || set -- ${P+$p} $* ${A+$p}
        done ; export PATH="$*" ; IFS=$oFS
}

% PATH=/usr/bin:/usr/yes/bin
% _path_assign \
    /usr/bin \
    /usr/yes/bin \
    /usr/bin/nope \
    -P \
    /usr/nope/bin \
    /usr/bin \
    -A \
    /nope/usr/bin \
    /usr/nope/bin

% echo $PATH

WYDAJNOŚĆ:

/usr/nope/bin:/usr/bin:/usr/yes/bin:/usr/bin/nope:/nope/usr/bin

Domyślnie będzie to -Awskazywać na $PATH, ale możesz zmienić to zachowanie, aby -Pnaprawić, dodając -Pdowolne miejsce na liście argumentów. Możesz przełączyć go z powrotem na -Adozowanie, wręczając go -Aponownie.

BEZPIECZNY EVAL

W większości przypadków zalecam, aby ludzie unikali jakiegokolwiek użycia eval. Ale to, jak sądzę, wyróżnia się na przykład na dobre. W tym przypadku jedynym stwierdzeniem, eval jakie kiedykolwiek można zobaczyć, jest P=lub A=. Wartości jego argumentów są ściśle sprawdzane przed wywołaniem. Po to eval jest.

assign() { oFS=$IFS ; IFS=: ; add=$* 
    set -- ${PATH:=$1} ; for p in $add ; do { 
        for d ; do [ -z "${d%"$p"}" ] && break 
        done ; } || set -- $* $p ; done
    PATH="$*" ; IFS=$oFS
}

To przyjmie tyle argumentów, ile podasz, i dodasz każdy z nich $PATHtylko raz i tylko wtedy, gdy jeszcze go nie ma $PATH. Wykorzystuje tylko w pełni przenośny skrypt powłoki POSIX, opiera się tylko na wbudowanych powłokach i jest bardzo szybki.

% PATH=/usr/bin:/usr/yes/bin
% assign \
    /usr/bin \
    /usr/yes/bin \
    /usr/nope/bin \
    /usr/bin \
    /nope/usr/bin \
    /usr/nope/bin

% echo "$PATH"
> /usr/bin:/usr/yes/bin:/usr/nope/bin:/nope/usr/bin

@ TAFKA'goldilocks 'zobacz aktualizację tutaj - zainspirowałeś mnie.
mikeserv

+1 Z ciekawości (być może byłoby to dobre osobne pytanie i odpowiedź), skąd wziął się pomysł, że _prefiksowanie funkcji powłoki powoduje, że są one odpowiednio rozmieszczone? W innych językach zwykle oznacza wewnętrzną funkcję globalną (to znaczy taką, która musi być globalna, ale nie jest przeznaczona do użytku zewnętrznego jako część interfejsu API). Moje imiona z pewnością nie są świetnymi wyborami, ale wydaje mi się, że samo użycie _wcale nie rozwiązuje problemów z kolizją - lepiej byłoby podać rzeczywistą przestrzeń nazw, np. mikeserv_path_assign().
złotowłosa

@ TAFKA „złote złociki” - lepiej byłoby z nim jeszcze bardziej sprecyzować, ale im dłużej nazwa staje się tym mniej wygodna, tym łatwiej jest z niej korzystać. Ale jeśli masz jakieś poprawne pliki binarne z prefiksem _, musisz zmienić menedżera pakietów. W każdym razie jest to po prostu „globalna, wewnętrzna funkcja” - jest globalna dla każdej powłoki wywoływanej z powłoki, w której została zadeklarowana, i jest tylko odrobiną zinterpretowanego skryptu językowego wiszącego w pamięci tłumacza . unix.stackexchange.com/questions/120528/…
mikeserv

Czy nie możesz unset a(lub odpowiednik) na końcu profilu?
sourcejedi

0

Ujrzeć! Wytrzymała przemysłowa 12-liniowa ... technicznie przenośna powłoka bash i zsh, która z oddaniem uwielbia wybrany przez Ciebie skrypt startowy ~/.bashrclub ~/.zshrcstartowy:

# void +path.append(str dirname, ...)
#
# Append each passed existing directory to the current user's ${PATH} in a
# safe manner silently ignoring:
#
# * Relative directories (i.e., *NOT* prefixed by the directory separator).
# * Duplicate directories (i.e., already listed in the current ${PATH}).
# * Nonextant directories.
+path.append() {
    # For each passed dirname...
    local dirname
    for   dirname; do
        # Strip the trailing directory separator if any from this dirname,
        # reducing this dirname to the canonical form expected by the
        # test for uniqueness performed below.
        dirname="${dirname%/}"

        # If this dirname is either relative, duplicate, or nonextant, then
        # silently ignore this dirname and continue to the next. Note that the
        # extancy test is the least performant test and hence deferred.
        [[ "${dirname:0:1}" == '/' &&
           ":${PATH}:" != *":${dirname}:"* &&
           -d "${dirname}" ]] || continue

        # Else, this is an existing absolute unique dirname. In this case,
        # append this dirname to the current ${PATH}.
        PATH="${PATH}:${dirname}"
    done

    # Strip an erroneously leading delimiter from the current ${PATH} if any,
    # a common edge case when the initial ${PATH} is the empty string.
    PATH="${PATH#:}"

    # Export the current ${PATH} to subprocesses. Although system-wide scripts
    # already export the ${PATH} by default on most systems, "Bother free is
    # the way to be."
    export PATH
}

Przygotujcie się na natychmiastową chwałę. Następnie, zamiast robić to i życzyć sobie najlepszego:

export PATH=$PATH:~/opt/bin:~/the/black/goat/of/the/woods/with/a/thousand/young

Zrób to zamiast tego i zapewnij sobie to, co najlepsze, niezależnie od tego, czy naprawdę tego chcesz, czy nie:

+path.append ~/opt/bin ~/the/black/goat/of/the/woods/with/a/thousand/young

Bardzo dobrze, określ „Najlepsze”.

Bezpieczne dołączanie i dopływanie do prądu ${PATH}nie jest banalną sprawą, na którą zwykle się składa. Choć wygodne i pozornie rozsądne, jednowarstwowe formy export PATH=$PATH:~/opt/binzachęcają do diabelskich komplikacji z:

  • Przypadkowe nazwy względne (np export PATH=$PATH:opt/bin.). Podczas gdy bashi zshpo cichu akceptuję i najczęściej ignoruję względne nazwy w większości przypadków, względne nazwy poprzedzone jednym hlub t(i prawdopodobnie innymi nikczemnymi bohaterami) powodują, że oba wstydliwie okaleczają się jako przełomowe arcydzieło Masakiego Kobayashiego z 1962 r. Harakiri :

    # Don't try this at home. You will feel great pain.
    $ PATH='/usr/local/bin:/usr/bin:/bin' && export PATH=$PATH:harakiri && echo $PATH
    /usr/local/bin:/usr/bin:arakiri
    $ PATH='/usr/local/bin:/usr/bin:/bin' && export PATH=$PATH:tanuki/yokai && echo $PATH
    binanuki/yokai   # Congratulations. Your system is now face-up in the gutter.
  • Przypadkowo zduplikowane nazwy. Chociaż zduplikowane ${PATH}nazwy są w dużej mierze nieszkodliwe, są również niepożądane, nieporęczne, nieco nieefektywne, utrudniają debuggowanie i promują zużycie dysku - coś w rodzaju takiej odpowiedzi. Podczas gdy dyski SSD w stylu NAND są ( oczywiście ) odporne na zużycie odczytu, dyski HDD nie są. Niepotrzebny dostęp do systemu plików przy każdej próbie polecenia oznacza niepotrzebne zużycie głowicy odczytu w tym samym tempie. Duplikaty są szczególnie nieprzyjemne, gdy wywołuje się zagnieżdżone skorupy w zagnieżdżonych podprocesach, w których to pozornie nieszkodliwe jednowarstwowe, jak export PATH=$PATH:~/watszybko, eksplodują w Siódmym Kręgu ${PATH}Piekła PATH=/usr/local/bin:/usr/bin:/bin:/home/leycec/wat:/home/leycec/wat:/home/leycec/wat:/home/leycec/wat. Tylko Belzebubba może ci pomóc, jeśli do tego dodasz dodatkowe nazwy. (Nie pozwól, aby stało się to z twoimi cennymi dziećmi. )

  • Przypadkowo brakuje nazwisk. Ponownie, chociaż brakujące ${PATH}nazwy są w dużej mierze nieszkodliwe, zazwyczaj są one również niepożądane, nieporęczne, lekko nieefektywne, utrudniają debuggowanie i sprzyjają zużyciu dysku.

Ergo, przyjazna automatyzacja, taka jak funkcja powłoki zdefiniowana powyżej. Musimy uratować się od siebie.

Ale ... Dlaczego „+ path.append ()”? Dlaczego nie po prostu append_path ()?

Dla disambiguity (na przykład za pomocą poleceń zewnętrznych obecnych ${PATH}lub na cały system funkcji powłoki zdefiniowane gdzie indziej), funkcje powłoki zdefiniowane przez użytkownika są idealnie przedrostkiem lub przyrostkiem unikalnych podciągów obsługiwanych przez bashi zsha, które są zabronione w standardzie basenames poleceń - takich jak, na przykład, +.

Hej. To działa. Nie osądzaj mnie.

Ale ... Dlaczego „+ path.append ()”? Dlaczego nie „+ path.prepend ()”?

Ponieważ ${PATH}dopływ do prądu jest bezpieczniejszy niż dopływ do prądu ${PATH}, wszystkie rzeczy są równe, którymi nigdy nie są. Zastępowanie poleceń systemowych za pomocą poleceń specyficznych dla użytkownika może być w najlepszym razie niehigieniczne, aw najgorszym stanie się szaleństwem. Na przykład w systemie Linux aplikacje podrzędne zwykle oczekują wariantów poleceń GNU coreutils , a nie niestandardowych niestandardowych pochodnych lub alternatyw.

To powiedziawszy, istnieją absolutnie uzasadnione przypadki użycia. Zdefiniowanie równoważnej +path.prepend()funkcji jest banalne. Sans prolix mgławica, bo on i jej wspólny rozsądek:

+path.prepend() {
    local dirname
    for dirname in "${@}"; do
        dirname="${dirname%/}"
        [[ "${dirname:0:1}" == '/' &&
           ":${PATH}:" != *":${dirname}:"* &&
           -d "${dirname}" ]] || continue
        PATH="${dirname}:${PATH}"
    done
    PATH="${PATH%:}"
    export PATH
}

Ale ... dlaczego nie Gilles?

Gilles ' Akceptowane odpowiedź gdzie indziej jest imponująco optymalna w ogólnym przypadku jako «shell agnostycznego idempotent append» . W przypadku wspólnego bashi zshze bez niepożądanych dowiązania jednak kara wydajność wymaga tego smuci się Ricer Gentoo we mnie. Nawet w obecności niepożądanych dowiązań symbolicznych można zastanawiać się, czy rozwidlenie jednej podpowłoki na add_to_PATH()argument jest warte potencjalnego wstawienia duplikatów dowiązań symbolicznych.

W przypadku ścisłych przypadków użycia, wymagających wyeliminowania nawet duplikatów dowiązań symbolicznych, ten zshwariant specyficzny robi to za pomocą wydajnych wbudowanych elementów zamiast nieefektywnych rozwidleń:

+path.append() {
    local dirname
    for   dirname in "${@}"; do
        dirname="${dirname%/}"
        [[ "${dirname:0:1}" == '/' &&
           ":${PATH}:" != *":${dirname:A}:"* &&
           -d "${dirname}" ]] || continue
        PATH="${PATH}:${dirname}"
    done
    PATH="${PATH#:}"
    export PATH
}

Uwaga *":${dirname:A}:"*raczej niż *":${dirname}:"*oryginał. :Ajest cudowny - zshniestety nieobecny pod większością innych powłok - w tym bash. Cytując man zshexpn:

Odp . : Zmień nazwę pliku na ścieżkę bezwzględną, podobnie jak amodyfikator, a następnie przekaż wynik przez funkcję realpath(3)biblioteki, aby rozwiązać dowiązania symboliczne. Uwaga: w systemach, które nie mają realpath(3)funkcji biblioteki, dowiązania symboliczne nie są rozwiązywane, więc w tych systemach ai Asą równoważne.

Żadnych dalszych pytań.

Nie ma za co. Ciesz się bezpiecznym ostrzałem. Zasługujesz teraz na to.


0

Oto moja wersja w stylu programowania funkcjonalnego.

  • Działa z każdą *PATHzmienną rozdzielaną dwukropkami , nie tylko PATH.
  • Nie ma dostępu do stanu globalnego
  • Działa tylko z / na jego niezmiennych wejściach
  • Tworzy jedno wyjście
  • Bez skutków ubocznych
  • Możliwość zapamiętania (w zasadzie)

Na uwagę zasługuje również:

  • Agnostyk dotyczący exporting; pozostawiono to dzwoniącemu (patrz przykłady)
  • Czysty bash; bez rozwidlenia
path_add () {
  # $ 1: Element, który ma się upewnić, że znajduje się w podanym ciągu ścieżki dokładnie raz
  # $ 2: Istniejąca wartość ciągu ścieżki („$ PATH”, a nie „PATH”)
  # 3 USD (opcjonalnie, cokolwiek): Jeśli podano, dołącz 1 USD; w przeciwnym razie dodaj
  #
  # Przykłady:
  # $ PATH eksportu = $ (path_add '/ opt / bin' "$ PATH”)
  # $ CDPATH = $ (path_add '/ Music' „$ CDPATH” at_end)

  local -r już_present = "(^ |:) $ {1} ($ | :)"
  if [["$ 2" = ~ $ już_present]]; następnie
    echo „2 USD”
  elif [[$ # == 3]]; następnie
    echo „$ {2}: $ {1}”
  jeszcze
    echo „$ {1}: $ {2}”
  fi
}

0

Ten skrypt umożliwia dodanie na końcu $PATH:

PATH=path2; add_to_PATH after path1 path2:path3
echo $PATH
path2:path1:path3

Lub dodaj na początku $PATH:

PATH=path2; add_to_PATH before path1 path2:path3
echo $PATH
path1:path3:path2

# Add directories to $PATH iff they're not already there
# Append directories to $PATH by default
# Based on https://unix.stackexchange.com/a/4973/143394
# and https://unix.stackexchange.com/a/217629/143394
add_to_PATH () {
  local prepend  # Prepend to path if set
  local prefix   # Temporary prepended path
  local IFS      # Avoid restoring for added laziness

  case $1 in
    after)  shift;; # Default is to append
    before) prepend=true; shift;;
  esac

  for arg; do
    IFS=: # Split argument by path separator
    for dir in $arg; do
      # Canonicalise symbolic links
      dir=$({ cd -- "$dir" && { pwd -P || pwd; } } 2>/dev/null)
      if [ -z "$dir" ]; then continue; fi  # Skip non-existent directory
      case ":$PATH:" in
        *":$dir:"*) :;; # skip - already present
        *) if [ "$prepend" ]; then
           # ${prefix:+$prefix:} will expand to "" if $prefix is empty to avoid
           # starting with a ":".  Expansion is "$prefix:" if non-empty.
            prefix=${prefix+$prefix:}$dir
          else
            PATH=$PATH:$dir  # Append by default
          fi;;
      esac
    done
  done
  [ "$prepend" ] && [ "$prefix" != "" ] && PATH=$prefix:$PATH
}
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.