#!/bin/sh -- # no runable script, just for vi
+TESTFAILURES=0
+
# we all like colorful messages
-CERROR="\e[1;31m" # red
-CWARNING="\e[1;33m" # yellow
-CMSG="\e[1;32m" # green
-CINFO="\e[1;96m" # light blue
-CDEBUG="\e[1;94m" # blue
-CNORMAL="\e[0;39m" # default system console color
-CDONE="\e[1;32m" # green
-CPASS="\e[1;32m" # green
-CFAIL="\e[1;31m" # red
-CCMD="\e[1;35m" # pink
+if expr match "$(readlink -f /proc/$$/fd/1)" '/dev/pts/[0-9]\+' > /dev/null && \
+ expr match "$(readlink -f /proc/$$/fd/2)" '/dev/pts/[0-9]\+' > /dev/null; then
+ CERROR="\e[1;31m" # red
+ CWARNING="\e[1;33m" # yellow
+ CMSG="\e[1;32m" # green
+ CINFO="\e[1;96m" # light blue
+ CDEBUG="\e[1;94m" # blue
+ CNORMAL="\e[0;39m" # default system console color
+ CDONE="\e[1;32m" # green
+ CPASS="\e[1;32m" # green
+ CFAIL="\e[1;31m" # red
+ CCMD="\e[1;35m" # pink
+fi
msgdie() { echo "${CERROR}E: $1${CNORMAL}" >&2; exit 1; }
msgwarn() { echo "${CWARNING}W: $1${CNORMAL}" >&2; }
msgnmsg() { echo -n "${CMSG}$1${CNORMAL}" >&2; }
msgninfo() { echo -n "${CINFO}I: $1${CNORMAL}" >&2; }
msgndebug() { echo -n "${CDEBUG}D: $1${CNORMAL}" >&2; }
-msgtest() { echo -n "${CINFO}$1 ${CCMD}$(echo "$2" | sed -e 's/^aptc/apt-c/' -e 's/^aptg/apt-g/' -e 's/^aptf/apt-f/')${CINFO} …${CNORMAL} " >&2; }
+msgtest() {
+ while [ -n "$1" ]; do
+ echo -n "${CINFO}$1${CCMD} " >&2;
+ echo -n "$(echo "$2" | sed -e 's/^aptc/apt-c/' -e 's/^aptg/apt-g/' -e 's/^aptf/apt-f/')${CINFO} " >&2;
+ shift
+ if [ -n "$1" ]; then shift; else break; fi
+ done
+ echo -n "…${CNORMAL} " >&2;
+}
msgpass() { echo "${CPASS}PASS${CNORMAL}" >&2; }
msgskip() { echo "${CWARNING}SKIP${CNORMAL}" >&2; }
-msgfail() { echo "${CFAIL}FAIL${CNORMAL}" >&2; }
+msgfail() { echo "${CFAIL}FAIL${CNORMAL}" >&2; TESTFAILURES=$((TESTFAILURES+1)); }
# enable / disable Debugging
MSGLEVEL=${MSGLEVEL:-3}
msgtest() { true; }
msgpass() { echo -n " ${CPASS}P${CNORMAL}" >&2; }
msgskip() { echo -n " ${CWARNING}S${CNORMAL}" >&2; }
- msgfail() { echo -n " ${CFAIL}FAIL${CNORMAL}" >&2; }
+ if [ -n "$CFAIL" ]; then
+ msgfail() { echo -n " ${CFAIL}FAIL${CNORMAL}" >&2; }
+ else
+ msgfail() { echo -n " ###FAILED###" >&2; }
+ fi
fi
if [ $MSGLEVEL -le 3 ]; then
msginfo() { true; }
}
aptconfig() { runapt apt-config $*; }
aptcache() { runapt apt-cache $*; }
+aptcdrom() { runapt apt-cdrom $*; }
aptget() { runapt apt-get $*; }
aptftparchive() { runapt apt-ftparchive $*; }
aptkey() { runapt apt-key $*; }
LD_LIBRARY_PATH=${BUILDDIRECTORY} $(which aptitude) $*
fi
}
+gdb() {
+ echo "gdb: run »$*«"
+ APT_CONFIG=aptconfig.conf LD_LIBRARY_PATH=${BUILDDIRECTORY} $(which gdb) ${BUILDDIRECTORY}/$1
+}
+
+exitwithstatus() {
+ # error if we about to overflow, but ...
+ # "255 failures ought to be enough for everybody"
+ if [ $TESTFAILURES -gt 255 ]; then
+ msgdie "Total failure count $TESTFAILURES too big"
+ fi
+ exit $((TESTFAILURES <= 255 ? TESTFAILURES : 255));
+}
addtrap() {
CURRENTTRAP="$CURRENTTRAP $1"
- trap "$CURRENTTRAP" 0 HUP INT QUIT ILL ABRT FPE SEGV PIPE TERM
+ trap "$CURRENTTRAP exitwithstatus;" 0 HUP INT QUIT ILL ABRT FPE SEGV PIPE TERM
}
setupenvironment() {
TMPWORKINGDIRECTORY=$(mktemp -d)
- local TESTDIR=$(readlink -f $(dirname $0))
+ TESTDIRECTORY=$(readlink -f $(dirname $0))
msgninfo "Preparing environment for ${CCMD}$(basename $0)${CINFO} in ${TMPWORKINGDIRECTORY}… "
- BUILDDIRECTORY="${TESTDIR}/../../build/bin"
+ BUILDDIRECTORY="${TESTDIRECTORY}/../../build/bin"
test -x "${BUILDDIRECTORY}/apt-get" || msgdie "You need to build tree first"
local OLDWORKINGDIRECTORY=$(pwd)
addtrap "cd /; rm -rf $TMPWORKINGDIRECTORY; cd $OLDWORKINGDIRECTORY;"
mkdir -p etc/apt/apt.conf.d etc/apt/sources.list.d etc/apt/trusted.gpg.d etc/apt/preferences.d
mkdir -p var/cache var/lib var/log
mkdir -p var/lib/dpkg/info var/lib/dpkg/updates var/lib/dpkg/triggers
- local STATUSFILE=$(echo "$(basename $0)" | sed -e 's/^test-/status-/' -e 's/^skip-/status-/')
- if [ -f "${TESTDIR}/${STATUSFILE}" ]; then
- cp "${TESTDIR}/${STATUSFILE}" var/lib/dpkg/status
- else
- touch var/lib/dpkg/status
- fi
touch var/lib/dpkg/available
mkdir -p usr/lib/apt
ln -s ${BUILDDIRECTORY}/methods usr/lib/apt/methods
cd ..
local PACKAGESFILE=$(echo "$(basename $0)" | sed -e 's/^test-/Packages-/' -e 's/^skip-/Packages-/')
- if [ -f "${TESTDIR}/${PACKAGESFILE}" ]; then
- cp "${TESTDIR}/${PACKAGESFILE}" aptarchive/Packages
+ if [ -f "${TESTDIRECTORY}/${PACKAGESFILE}" ]; then
+ cp "${TESTDIRECTORY}/${PACKAGESFILE}" aptarchive/Packages
fi
local SOURCESSFILE=$(echo "$(basename $0)" | sed -e 's/^test-/Sources-/' -e 's/^skip-/Sources-/')
- if [ -f "${TESTDIR}/${SOURCESSFILE}" ]; then
- cp "${TESTDIR}/${SOURCESSFILE}" aptarchive/Sources
+ if [ -f "${TESTDIRECTORY}/${SOURCESSFILE}" ]; then
+ cp "${TESTDIRECTORY}/${SOURCESSFILE}" aptarchive/Sources
fi
- cp $(find $TESTDIR -name '*.pub' -o -name '*.sec') keys/
+ cp $(find $TESTDIRECTORY -name '*.pub' -o -name '*.sec') keys/
ln -s ${TMPWORKINGDIRECTORY}/keys/joesixpack.pub rootdir/etc/apt/trusted.gpg.d/joesixpack.gpg
echo "Dir \"${TMPWORKINGDIRECTORY}/rootdir\";" > aptconfig.conf
echo "Dir::state::status \"${TMPWORKINGDIRECTORY}/rootdir/var/lib/dpkg/status\";" >> aptconfig.conf
echo "DPKG::options:: \"--root=${TMPWORKINGDIRECTORY}/rootdir\";" >> aptconfig.conf
echo "DPKG::options:: \"--force-not-root\";" >> aptconfig.conf
echo "DPKG::options:: \"--force-bad-path\";" >> aptconfig.conf
+ if ! $(which dpkg) --assert-multi-arch 2>&1 > /dev/null; then
+ echo "DPKG::options:: \"--force-architecture\";" >> aptconfig.conf # Added to test multiarch before dpkg is ready for it…
+ fi
echo "DPKG::options:: \"--log=${TMPWORKINGDIRECTORY}/rootdir/var/log/dpkg.log\";" >> aptconfig.conf
echo 'quiet::NoUpdate "true";' >> aptconfig.conf
export LC_ALL=C
msgdone "info"
}
+getarchitecture() {
+ if [ "$1" = "native" -o -z "$1" ]; then
+ eval `aptconfig shell ARCH APT::Architecture`
+ if [ -n "$ARCH" ]; then
+ echo $ARCH
+ else
+ dpkg --print-architecture
+ fi
+ else
+ echo $1
+ fi
+}
+
+getarchitectures() {
+ echo "$(aptconfig dump | grep APT::Architecture | cut -d'"' -f 2 | sed '/^$/ d' | sort | uniq | tr '\n' ' ')"
+}
+
configarchitecture() {
local CONFFILE=rootdir/etc/apt/apt.conf.d/01multiarch.conf
- echo "APT::Architecture \"$1\";" > $CONFFILE
+ rm -f $CONFFILE
+ echo "APT::Architecture \"$(getarchitecture $1)\";" > $CONFFILE
shift
while [ -n "$1" ]; do
- echo "APT::Architectures:: \"$1\";" >> $CONFFILE
+ echo "APT::Architectures:: \"$(getarchitecture $1)\";" >> $CONFFILE
shift
done
+ configdpkg
+}
+
+configdpkg() {
+ if [ ! -e rootdir/var/lib/dpkg/status ]; then
+ local STATUSFILE=$(echo "$(basename $0)" | sed -e 's/^test-/status-/' -e 's/^skip-/status-/')
+ if [ -f "${TESTDIRECTORY}/${STATUSFILE}" ]; then
+ cp "${TESTDIRECTORY}/${STATUSFILE}" rootdir/var/lib/dpkg/status
+ else
+ echo -n > rootdir/var/lib/dpkg/status
+ fi
+ fi
+ if $(which dpkg) --assert-multi-arch 2>&1 > /dev/null; then
+ local ARCHS="$(getarchitectures)"
+ if echo "$ARCHS" | grep -E -q '[^ ]+ [^ ]+'; then
+ DPKGARCH="$(dpkg --print-architecture)"
+ for ARCH in ${ARCHS}; do
+ if [ "${ARCH}" != "${DPKGARCH}" ]; then dpkg --add-architecture ${ARCH}; fi
+ done
+ if [ "0" = "$(dpkg -l dpkg 2> /dev/null | grep '^i' | wc -l)" ]; then
+ # dpkg doesn't really check the version as long as it is fully installed,
+ # but just to be sure we choose one above the required version
+ insertinstalledpackage 'dpkg' "all" '1.16.2+fake'
+ fi
+ fi
+ fi
}
setupsimplenativepackage() {
Section: $SECTION
Priority: $PRIORITY
Maintainer: Joe Sixpack <joe@example.org>
-Standards-Version: 3.9.1
+Standards-Version: 3.9.3" > ${BUILDDIR}/debian/control
+ local BUILDDEPS="$(echo "$DEPENDENCIES" | grep '^Build-')"
+ test -z "$BUILDDEPS" || echo "$BUILDDEPS" >> ${BUILDDIR}/debian/control
+ echo "
+Package: $NAME" >> ${BUILDDIR}/debian/control
-Package: $NAME" > ${BUILDDIR}/debian/control
if [ "$ARCH" = 'all' ]; then
echo "Architecture: all" >> ${BUILDDIR}/debian/control
else
echo "Architecture: any" >> ${BUILDDIR}/debian/control
fi
- test -z "$DEPENDENCIES" || echo "$DEPENDENCIES" >> ${BUILDDIR}/debian/control
+ local DEPS="$(echo "$DEPENDENCIES" | grep -v '^Build-')"
+ test -z "$DEPS" || echo "$DEPS" >> ${BUILDDIR}/debian/control
if [ -z "$DESCRIPTION" ]; then
echo "Description: an autogenerated dummy ${NAME}=${VERSION}/${RELEASE}
If you find such a package installed on your system,
YOU did something horribly wrong! They are autogenerated
und used only by testcases for APT and surf no other propose…" >> ${BUILDDIR}/debian/control
else
- echo "Description: $DESCRIPTION" >> ${BUILDIR}/debian/control
+ echo "Description: $DESCRIPTION" >> ${BUILDDIR}/debian/control
fi
+
echo '3.0 (native)' > ${BUILDDIR}/debian/source/format
local SRCS="$( (cd ${BUILDDIR}/..; dpkg-source -b ${NAME}-${VERSION} 2>&1) | grep '^dpkg-source: info: building' | grep -o '[a-z0-9._+~-]*$')"
for SRC in $SRCS; do
echo "pool/${SRC}" >> ${BUILDDIR}/../${RELEASE}.${DISTSECTION}.srclist
done
- for arch in $(echo "$ARCH" | sed -e 's#,#\n#g'); do
+ for arch in $(echo "$ARCH" | sed -e 's#,#\n#g' | sed -e "s#^native\$#$(getarchitecture 'native')#"); do
rm -rf ${BUILDDIR}/debian/tmp
mkdir -p ${BUILDDIR}/debian/tmp/DEBIAN ${BUILDDIR}/debian/tmp/usr/share/doc/${NAME} ${BUILDDIR}/debian/tmp/usr/bin
cp ${BUILDDIR}/debian/copyright ${BUILDDIR}/debian/changelog ${BUILDDIR}/FEATURES ${BUILDDIR}/debian/tmp/usr/share/doc/${NAME}
local BUILDDIR=$1
local RELEASE=$2
local SECTION=$3
+ local ARCH=$(getarchitecture $4)
msgninfo "Build package $(echo "$BUILDDIR" | grep -o '[^/]*$') for ${RELEASE} in ${SECTION}… "
cd $BUILDDIR
if [ "$ARCH" = "all" ]; then
local ARCHS="$(find pool/ -name '*.deb' | grep -oE '_[a-z0-9-]+\.deb$' | sort | uniq | sed -e '/^_all.deb$/ d' -e 's#^_\([a-z0-9-]*\)\.deb$#\1#' | tr '\n' ' ')"
if [ -z "$ARCHS" ]; then
# the pool is empty, so we will operate on faked packages - let us use the configured archs
- ARCHS="$(aptconfig dump | grep APT::Architecture | cut -d'"' -f 2 | sed '/^$/ d' | sort | uniq | tr '\n' ' ')"
+ ARCHS="$(getarchitectures)"
fi
echo -n 'Dir {
ArchiveDir "' >> ftparchive.conf
local DEPENDENCIES="$5"
local PRIORITY="${6:-optional}"
local ARCHS=""
- for arch in $(echo "$ARCH" | sed -e 's#,#\n#g'); do
- if [ "$arch" = "all" ]; then
- ARCHS="$(aptconfig dump | grep APT::Architecture | cut -d'"' -f 2 | sed '/^$/ d' | sort | uniq | tr '\n' ' ')"
+ for arch in $(echo "$ARCH" | sed -e 's#,#\n#g' | sed -e "s#^native\$#$(getarchitecture 'native')#"); do
+ if [ "$arch" = 'all' -o "$arch" = 'none' ]; then
+ ARCHS="$(getarchitectures)"
else
ARCHS="$arch"
fi
Priority: $PRIORITY
Section: other
Installed-Size: 42
-Maintainer: Joe Sixpack <joe@example.org>
-Architecture: $arch
-Version: $VERSION
+Maintainer: Joe Sixpack <joe@example.org>" >> $FILE
+ test "$arch" = 'none' || echo "Architecture: $arch" >> $FILE
+ echo "Version: $VERSION
Filename: pool/main/${NAME}/${NAME}_${VERSION}_${arch}.deb" >> $FILE
test -z "$DEPENDENCIES" || echo "$DEPENDENCIES" >> $FILE
echo "Description: an autogenerated dummy ${NAME}=${VERSION}/${RELEASE}
done
}
+insertsource() {
+ local RELEASE="$1"
+ local NAME="$2"
+ local ARCH="$3"
+ local VERSION="$4"
+ local DEPENDENCIES="$5"
+ local ARCHS=""
+ local SPATH="aptarchive/dists/${RELEASE}/main/source"
+ mkdir -p $SPATH
+ local FILE="${SPATH}/Sources"
+ echo "Package: $NAME
+Binary: $NAME
+Version: $VERSION
+Maintainer: Joe Sixpack <joe@example.org>
+Architecture: $ARCH" >> $FILE
+ test -z "$DEPENDENCIES" || echo "$DEPENDENCIES" >> $FILE
+ echo "Files:
+ d41d8cd98f00b204e9800998ecf8427e 0 ${NAME}_${VERSION}.dsc
+ d41d8cd98f00b204e9800998ecf8427e 0 ${NAME}_${VERSION}.tar.gz
+" >> $FILE
+}
+
insertinstalledpackage() {
local NAME="$1"
local ARCH="$2"
local VERSION="$3"
local DEPENDENCIES="$4"
local PRIORITY="${5:-optional}"
- local FILE="rootdir/var/lib/dpkg/status"
- for arch in $(echo "$ARCH" | sed -e 's#,#\n#g'); do
+ local STATUS="${6:-install ok installed}"
+ local FILE='rootdir/var/lib/dpkg/status'
+ local INFO='rootdir/var/lib/dpkg/info'
+ for arch in $(echo "$ARCH" | sed -e 's#,#\n#g' | sed -e "s#^native\$#$(getarchitecture 'native')#"); do
echo "Package: $NAME
-Status: install ok installed
+Status: $STATUS
Priority: $PRIORITY
Section: other
Installed-Size: 42
Maintainer: Joe Sixpack <joe@example.org>
-Architecture: $arch
Version: $VERSION" >> $FILE
+ test "$arch" = 'none' || echo "Architecture: $arch" >> $FILE
test -z "$DEPENDENCIES" || echo "$DEPENDENCIES" >> $FILE
echo "Description: an autogenerated dummy ${NAME}=${VERSION}/installed
If you find such a package installed on your system,
YOU did something horribly wrong! They are autogenerated
und used only by testcases for APT and surf no other propose…
" >> $FILE
+ if [ "$(dpkg-query -W --showformat='${Multi-Arch}')" = 'same' ]; then
+ echo -n > ${INFO}/${NAME}:${arch}.list
+ else
+ echo -n > ${INFO}/${NAME}.list
+ fi
done
}
msgninfo "\t${line} file… "
cat ${line} | gzip > ${line}.gz
cat ${line} | bzip2 > ${line}.bz2
- cat ${line} | lzma > ${line}.lzma
+ cat ${line} | xz --format=lzma > ${line}.lzma
cat ${line} | xz > ${line}.xz
msgdone "info"
done
generatereleasefiles
}
+# can be overridden by testcases for their pleasure
+getcodenamefromsuite() { echo -n "$1"; }
+getreleaseversionfromsuite() { true; }
+getlabelfromsuite() { true; }
+
generatereleasefiles() {
+ # $1 is the Date header and $2 is the ValidUntil header to be set
+ # both should be given in notation date/touch can understand
msgninfo "\tGenerate Release files… "
- local DATE="${1:-now}"
if [ -e aptarchive/dists ]; then
- for dir in $(find ./aptarchive/dists -mindepth 3 -maxdepth 3 -type d -name 'i18n'); do
- aptftparchive -qq release $dir -o APT::FTPArchive::Release::Patterns::='Translation-*' > $dir/Index
- done
for dir in $(find ./aptarchive/dists -mindepth 1 -maxdepth 1 -type d); do
- local CODENAME="$(echo "$dir" | cut -d'/' -f 4)"
- aptftparchive -qq release $dir -o APT::FTPArchive::Release::Suite="${CODENAME}" -o APT::FTPArchive::Release::Codename="${CODENAME}" | sed -e '/0 Release$/ d' > $dir/Release # remove the self reference
- if [ "$CODENAME" = "experimental" -o "$CODENAME" = "experimental2" ]; then
+ local SUITE="$(echo "$dir" | cut -d'/' -f 4)"
+ local CODENAME="$(getcodenamefromsuite $SUITE)"
+ local VERSION="$(getreleaseversionfromsuite $SUITE)"
+ local LABEL="$(getlabelfromsuite $SUITE)"
+ if [ -n "$VERSION" ]; then
+ VERSION="-o APT::FTPArchive::Release::Version=${VERSION}"
+ fi
+ if [ -n "$LABEL" ]; then
+ LABEL="-o APT::FTPArchive::Release::Label=${LABEL}"
+ fi
+ aptftparchive -qq release $dir \
+ -o APT::FTPArchive::Release::Suite="${SUITE}" \
+ -o APT::FTPArchive::Release::Codename="${CODENAME}" \
+ ${LABEL} \
+ ${VERSION} \
+ | sed -e '/0 Release$/ d' > $dir/Release # remove the self reference
+ if [ "$SUITE" = "experimental" -o "$SUITE" = "experimental2" ]; then
sed -i '/^Date: / a\
NotAutomatic: yes' $dir/Release
fi
+ if [ -n "$1" -a "$1" != "now" ]; then
+ sed -i "s/^Date: .*$/Date: $(date -d "$1" '+%a, %d %b %Y %H:%M:%S %Z')/" $dir/Release
+ fi
+ if [ -n "$2" ]; then
+ sed -i "/^Date: / a\
+Valid-Until: $(date -d "$2" '+%a, %d %b %Y %H:%M:%S %Z')" $dir/Release
+ fi
done
else
aptftparchive -qq release ./aptarchive | sed -e '/0 Release$/ d' > aptarchive/Release # remove the self reference
fi
- if [ "$DATE" != "now" ]; then
+ if [ -n "$1" -a "$1" != "now" ]; then
for release in $(find ./aptarchive -name 'Release'); do
touch -d "$1" $release
done
if which weborf > /dev/null; then
weborf -xb aptarchive/ 2>&1 > /dev/null &
addtrap "kill $!;"
+ elif which gatling > /dev/null; then
+ cd aptarchive
+ gatling -p 8080 -F -S 2>&1 > /dev/null &
+ addtrap "kill $!;"
+ cd - > /dev/null
elif which lighttpd > /dev/null; then
echo "server.document-root = \"$(readlink -f ./aptarchive)\"
server.port = 8080
return 0
}
+changetocdrom() {
+ mkdir -p rootdir/media/cdrom/.disk
+ local CD="$(readlink -f rootdir/media/cdrom)"
+ echo "acquire::cdrom::mount \"${CD}\";" > rootdir/etc/apt/apt.conf.d/00cdrom
+ echo 'acquire::cdrom::autodetect 0;' >> rootdir/etc/apt/apt.conf.d/00cdrom
+ echo -n "$1" > ${CD}/.disk/info
+ if [ ! -d aptarchive/dists ]; then
+ msgdie 'Flat file archive cdroms can not be created currently'
+ return 1
+ fi
+ mv aptarchive/dists $CD
+ ln -s "$(readlink -f ./incoming)" $CD/pool
+ find rootdir/etc/apt/sources.list.d/ -name 'apt-test-*.list' -delete
+}
+
checkdiff() {
local DIFFTEXT="$($(which diff) -u $* | sed -e '/^---/ d' -e '/^+++/ d' -e '/^@@/ d')"
if [ -n "$DIFFTEXT" ]; then
fi
}
+testempty() {
+ msgtest "Test for no output of" "$*"
+ test -z "$($* 2>&1)" && msgpass || msgfail
+}
+
testequal() {
local COMPAREFILE=$(mktemp)
addtrap "rm $COMPAREFILE;"
N: No packages found"
local COMPAREFILE=$(mktemp)
addtrap "rm $COMPAREFILE;"
- local ARCH=$(dpkg-architecture -qDEB_HOST_ARCH_CPU)
- eval `apt-config shell ARCH APT::Architecture`
+ local ARCH="$(getarchitecture 'native')"
echo "$VIRTUAL" | sed -e "s/:$ARCH//" -e 's/:all//' > $COMPAREFILE
aptcache show -q=0 $PACKAGE 2>&1 | checkdiff $COMPAREFILE - && msgpass || msgfail
}
testdpkginstalled() {
msgtest "Test for correctly installed package(s) with" "dpkg -l $*"
- local PKGS="$(dpkg -l $* | grep '^i' | wc -l)"
+ local PKGS="$(dpkg -l $* 2>/dev/null | grep '^i' | wc -l)"
if [ "$PKGS" != $# ]; then
echo $PKGS
dpkg -l $* | grep '^[a-z]'