Pokazywanie postów oznaczonych etykietą set. Pokaż wszystkie posty
Pokazywanie postów oznaczonych etykietą set. 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

środa, 8 lipca 2009

Bash - PIPESTATUS

W jednym z pierwszych blogów zadałem pytanie dotyczące wykrywania błędu przy wykonaniu poleceń w potoku (pipe).

W wypadku jakichkolwiek skryptów ważne jest pilnowanie poprawności wykonania komend. Każda wykonana komenda zwraca status zakończenia, który jeśli wszystko przebiegło poprawnie powinien być równy zero.
Sprawdźmy to:

# ls
# echo $?
0


Co jednak w wypadku gdy korzystamy z potoku?
Weźmy przykład:

cat plik | gzip > plik2.gz


Co sie stanie jeśli plik nie istnieje? Wykonajmy to polecenie:

cat: plik: No such file or directory
# echo $?
0


Status wykonania polecenia poprawny (zero). Dlaczego? Ponieważ zmienna $? przechowuje stan wykonania ostatniego polecenia. W naszym przykładzie błąd wystąpił przy pierwszym poleceniu - wyświetleniu zawartości pliku. Ponieważ plik nie istniał został wyświetlony błąd - jednak operacja była kontynuowana w potoku. Polecenie gzip wykonało się poprawnie - skompresowało zerowej wielkości strumień wejściowy. Zwrócony status jest więc jak najbardziej w tym wypadku właściwy - mimo, że cała operacja w potoku nie wykonała się po naszej myśli.
W takim wypadku z pomocą przychodzi zmienna $PIPESTATUS - tabela statusów zakończenia poleceń w potoku. Każda kolejna komórka tabeli zawiera status wykonania poszczególnych poleceń. Zobaczmy jak to działa:

cat plik |gzip > plik2.gz
cat: plik: No such file or directory
# echo ${PIPESTATUS[*]}
1 0


Mamy niepoprawny status wykonania cat - jest więc możliwość stwierdzenia błędu wykonania któregoś z poleceń w potoku.
Możemy odwoływać się do poszczególnych komórek tabeli za pomocą indeksu, np. dla pierwszego polecenia będzie to ${PIPESTATUS[0]}. Należy jednak pamiętać o tym, że zawartość zmiennej PIPESTATUS jest 'ulotna', tzn. dostępna jest tylko bezpośrednio po wykonaniu polecenia. Dla zrozumienia pokażę to na poniższym przykładzie:

cat plik |gzip > plik2.gz
cat: plik: No such file or directory
# echo ${PIPESTATUS[*]}
1 0
# echo ${PIPESTATUS[*]}
0

Drugie wykonanie zwraca status wykonania polecenia echo.

W wypadku dłuższych skryptów istnieje możliwość przerwania przetwarzania skryptu w wypadku wystąpienia błędu za pomocą komendy set -e. Myślę, że jest to wygodniejsze niż śledzenie statusów po każdym wywołaniu (chociaż jak zawsze to zależy od tego co robimy).

PS. Możemy wykluczyć poszczególne polecenia w skrypcie z "ostrej" obsługi błędów przy włączonym set -e. Robimy to poprzedzając dane polecenie znakiem wykrzyknika '!'. Polecam manual set

Przydatne odnośniki:
http://tldp.org/LDP/abs/html/internalvariables.html