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