Pokazywanie postów oznaczonych etykietą bash. Pokaż wszystkie posty
Pokazywanie postów oznaczonych etykietą bash. Pokaż wszystkie posty

poniedziałek, 13 lipca 2009

Co nieco o euse i innych

Ponieważ pewnie wiele osób zauważyło, że euse nie jest demonem szybkości, warto zastanowić się nad alternatywami. Wcześniej jednak spróbujmy znaleźć powód wolnego działania euse.
Zaczynamy!
Gdzie znajduje się program euse:

# which euse

/usr/bin/euse

OK, mamy. Teraz nauczmy się wykorzystywać przydatne skróty shella. Strzałki w górę i dół na klawiaturze umożliwiają nam nawigację w historii wykonanych poleceń. Więc strzałka w górę i mamy poprzednio wykonane polecenie. Teraz naciśnijmy HOME i jesteśmy na początku linii, END - na jej końcu. O tym jak konfigurować własne skróty opowiem innym razem.
Wykorzystując tą wiedzę co by się za dużo nie napisać zmodyfikujmy tę linię, tak żeby zobaczyć czym jaki jest typ pliku euse:

# file `which euse`
/usr/bin/euse: Bourne-Again shell script text executable


aha - czyli skrypt Bash. Polecenie file wyświetla nam na podstawie swojej bazy informacje na temat typu pliku (polecam do szczegółowej lektury man).
Czym jest zaś tajemniczy ` - backquote (backtick)?
W tym miejscu następuje podmiana wywołania polecenia na wartość zwróconą po jego wykonaniu (command substitution lub bardziej po polsku - cytowanie polecenia). Czyli wszystko co znajdzie się pomiędzy znakami ` jest wykonywane a następnie wynik zwrócony jest podstawiany w to miejsce. Wywołania z użyciem backtick'ów (odwrotny apostrof) można zagnieżdżać, należy jednak pamiętać wtedy o użyciu znaku maskującego \ (tzw. backslasha). Backtick jest szczególnie użyteczny w skryptach, gdy chcemy podstawić gdzie możemy wykorzystać go do podstawienia wyniku wykonania polecenia pod zmienną (np.: ZMIENNA=`ls -l`). Zamiennie z backtickiem można stosować konstrukcję $(polecenie).

Skoro zaczynamy powoli wchodzić w shella nasuwają mi się dwa pytanka, a mianowicie:
- po czym poznać czy dana komenda to polecenie zewnętrzne, czy wbudowana komenda basha?
- dlaczego cd jest wbudowany w bash?

Zostawiam je na razie jako zadanie domowe - spróbujcie pogłówkować.

Teraz gdy dowiedzieliśmy się paru nowych rzeczy warto popatrzyc w źródła euse (nie zapomnijcie o wykorzystywaniu opisanych skrótów):

vim `which euse`

Ponieważ - jak stwierdziliśmy wcześniej - wyświetlanie informacji o flagach jest wolne skupmy się na tej operacji i trochę pogmerajmy w kodzie. Informacje wyświetlamy za pomocą opcji -i - poszukajmy:

while [ -n "${1}" ]; do
case "${1}" in
-h | --help) MODE="showhelp";;
-v | --version) MODE="showversion";;
-i | --info) MODE="showdesc";;
-I | --info-installed) MODE="showinstdesc";;
-l | --local) SCOPE="local";;
-g | --global) SCOPE="global";;
-a | --active) MODE="showflags";;
-E | --enable) MODE="modify"; ACTION="add";;
-D | --disable) MODE="modify"; ACTION="remove";;
-P | --prune) MODE="modify"; ACTION="prune";;
-*)


Jest w pętli odpowiedzialnej za odczyt argumentów wywołania (opcji) programu/skryptu (o tym innym razem). Jak widać (mniej lub bardziej) użycie tej opcji powoduje ustawienie w skrypcie zmiennej MODE na wartość showdesc.
Co się dzieje dalej?
Na samym końcu skryptu znajdziemy:

##### main program comes now #####

# disable globbing as it fucks up with args=*
set -f
parse_arguments "$@"
check_sanity

eval ${MODE} ${ARGUMENTS}
set +f


Jak widać mamy tu cały przepływ programu - kolejno wywołanie funkcji (zdefiniowanych powyżej) parse_arguments (parsowanie opcji), check_sanity (pomijam - pewnie sprawdzanie poprawności wywołania, opcji, etc.) i wykonanie funkcji o nazwie zdefiniowanej w zmiennej MODE (i wszystko się wyjaśniło - o eval powiem innym razem). Zaglądnijmy do tej funkcji (showdesc):

# This function takes a list of use flags and shows the status and
# the description for each one, honoring $SCOPE
showdesc() {
local descdir
local current_desc
local found_one
local args

args="${*:-*}"

if [ -z "${SCOPE}" ]; then
SCOPE="global" showdesc ${args}
echo
SCOPE="local" showdesc ${args}
return
fi

descdir="$(get_portdir)/profiles"

[...]

while [ -n "${1}" ]; do
if [ "${SCOPE}" == "global" ]; then
if grep "^${1} *-" "${descdir}/use.desc" > /dev/null; then
get_flagstatus "${1}"
foundone=1
fi
grep "^${1} *-" "${descdir}/use.desc"
fi
# local flags are a bit more complicated as there can be multiple
# entries per flag and we can't pipe into printf
if [ "${SCOPE}" == "local" ]; then
if grep ":${1} *-" "${descdir}/use.local.desc" > /dev/null; then
foundone=1
fi
grep ":${1} *-" "${descdir}/use.local.desc" \
| sed -e "s/^\([^:]\+\):\(${1}\) *- *\(.\+\)/\1|\2|\3/g" \
| while read line; do
pkg="$(echo $line | cut -d\| -f 1)"
flag="$(echo $line | cut -d\| -f 2)"
desc="$(echo $line | cut -d\| -f 3)"
get_flagstatus "${flag}"
printf "%s (%s):\n%s\n\n" "${flag}" "${pkg}" "${desc}"
done
fi
shift
done


Przy okazji takiego grzebania można sie trochę nauczyć i dowiedzieć o Gentoo i portage'u. Jak widać opisy flag globalnych - znajdują się w pliku profiles/use.desc w katalogu głównym systemu portage, natomiast flagi zdefiniowane w poszczególnych paczkach (lokalne) - w profiles/use.local.desc.
Patrząc na pętle łatwo zauważyć, że wyszukiwanie odbywa się za pomocą polecenia grep (o tym innym razem). W wypadku flag globalnych operacja jest dość szybka, jednak komplikuje się i wydłuża przy flagach lokalnych ze względu na ich format (warto zaglądnąć do tych plików). No i już wiemy dlaczego to tyle trwa.

Przeglądając źródła warto zwrócić jeszcze uwagę na komendę set +f/set -f, która odpowiada za obsługę globbingu w shellu - w skrócie ubogiego krewnego wyrażeń regularnych (o tym innym razem). Tu została zastosowana, żeby ominąć jej rozwinięcie/ewaluację w shellu - widać chłopaki mieli pewien problem z tym przy wywołaniu.

PS. Następnym razem opowiem o innych narzędziach do zarządzania flagami.
Zmieniam też nieco tryb pisania z pn-sobota na pn/sr/pt

piątek, 26 czerwca 2009

Zdalny pipe

Dziś kolega 'junior admin' podniecił się strasznie możliwością przekierowywania standardowego wyjścia programu na standardowe wejście kolejnego między lokalnym i zdalnym systemem, czyli tzw. pipem. Zacznijmy od dwóch przykładów, które mi podesłał:
tar cf - whatever | ssh remotehost " ( cd /some/path ; tar xf - ) "
ssh remotehost "( cd /somewhere ; tar cf - something ) " | tar xf -
W pierwszej linijce pakujemy (bez kompresji) zadaną jako drugi parametr listę plików, pierwszym parametrem jest w tym wypadku znak '-' często używany jeśli chcemy uzyskać coś na STDOUT zamiast zapisać gdzieś w pliku, gdy program to umożliwia. Tak więc znak '-' informuje polecenie tar, że zamiast zapisywać tworzone archiwum do pliku ma wyrzucić wynik na standardowe wyjście (czyli w linuksie plik o deskryptorze 1).
Następnie mamy | czyli przekierowanie/przekazanie standardowego wyjścia poprzednio wykonywanej komendy (opisanej już jedyneczki) na standardowe wejście kolejnej komendy (STDIN - czyli deskryptor pliku o identyfikatorze 0 [zero])
Kolejnym poleceniem jest wywołanie ssh - nawiąż połączenie z hostem remotehost i wykonaj polecenia podane jako kolejny parametr. Podajemy je w cudzysłowiu, żeby bash w trakcie przetwarzania nie potraktował spacji jako separatora kolejnych argumentów (pewnie poleciałyby błedy - można spróbować - na pewno coś 'chorego' się stanie. O interpretacji linii poleceń przez shell to może innym razem - jak ktoś z czytelników będzie bardzo tego chciał).
Pozostaje więc ostatnia część:
cd /some/path ; tar xf -
To już dosyć proste - zmieniamy ścieżkę i rozpakowujemy archiwum poleceniem tar (x - extract). Pojawia się znowu magiczny minus. W wypadku rozpakowywania (jak co poniektórzy pewnie się domyślają) minus mówi o tym, że źródłem z którego należy czytać nie jest plik a standardowe wejście (przytaczane wcześniej zero).

Drugi przypadek pozostawiam do samodzielnej analizy. W razie problemów i pytań służę pomocą

Do samodzielnej lektury polecam wikipedię
Co mi tu jeszcze ciekawego przyszło do głowy. Każdy program po wykonaniu zwraca status wykonania w postaci liczbowej. Znajdziemy go w zmiennej shella $?:
echo $?
Jeżeli program wykonał się poprawnie - nie pojawiły się błędy - to status powinien być równy 0 [zero]. W przypadku problemów będzie to cyfra różna od zero - to co będzie oznaczać zależy już od programu. Warto sprawdzać tą zmienną pisząc skrypty - przez co możemy przerwać je w odpowiednim momencie, gdy coś pójdzie nie tak a nie zostawić wszystko dalszemu biegowi wypadków.
A co w wypadku gdy łączymy kilka poleceń w pipe? Jak wykryć, że któreś (nieostatnie) polecenie wykonało się niepoprawnie?

Odpowiedź w przyszłym blogu...
Może warto poszukać samemu?

Czekam na komentarze...