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

Brak komentarzy:

Prześlij komentarz