diff --git a/bin/showgroup.py b/bin/showgroup.py
index c74c8b848a0acd4a4f321d788dc887355fb9ee7e..266ebeae99269e2f3cb1e9842ccd1e03a88b1681 100755
--- a/bin/showgroup.py
+++ b/bin/showgroup.py
@@ -5,10 +5,8 @@ import argparse
 import collections
 import lib_path
 import lib
-import tabulate
 import dphysldap
 
-
 OPTS = collections.OrderedDict()
 OPTS['access'] = 'accessRight'
 OPTS['blocked'] = 'blocked'
@@ -18,56 +16,49 @@ OPTS['mail'] = 'mail'
 OPTS['telephone'] = 'telephoneNumber'
 
 
-def main():
+def get_args(opts=dict()):
     parser = argparse.ArgumentParser(
-        add_help=False, description='Show group members')
-    parser.add_argument('group', help='The group name (cn)')
-    for k, v in OPTS.items():
+        add_help=False, description='Show groups or group members')
+    parser.add_argument('group', help='The group name (cn), or wildcards')
+    for k, v in opts.items():
         parser.add_argument('-' + k[:1], '--' + k, dest=k,
                             action='store_const', const=True,
-                            help='Show ' + v + ' column')
+                            help='Show ' + v)
     parser.add_argument('--help', action='help',
                         help='Show this help message and exit')
-    arg = vars(parser.parse_args())
-
-    ldap = dphysldap.Ldap()
+    return vars(parser.parse_args())
 
-    group_query = 'cn: {0}'.format(arg['group'])
-    group_attrs = ['cn', 'gidNumber', 'memberUid']
-    groups = ldap.get_groups(query=group_query, attributes=group_attrs)
 
-    if len(groups) != 1:
-        sys.exit('error: number of groups matched: {0}'.format(len(groups)))
-    group = groups[0]
+def main():
+    args = get_args(OPTS)
 
-    if 'memberUid' not in group:
-        sys.exit('error: empty group')
-    members = group['memberUid']
-    user_query = 'uid:' + ';'.join(members)
-    user_attrs = ['uid', 'uidNumber', 'gecos']
+    ldap = dphysldap.Ldap()
 
-    for k, v in OPTS.items():
-        if arg[k]:
-            user_attrs.append(v)
+    groups = dphysldap.Groups(ldap, ['cn', 'gidNumber', 'memberUid'])
+    groups.search(args['group'])
 
-    users = ldap.get_users(query=user_query, attributes=user_attrs)
+    if not groups:
+        sys.exit('No groups found.')
 
-    if not users:
-        sys.exit('error: no users found')
+    if len(groups) != 1:
+        #groups.sort('cn')
+        print(groups)
 
-    cn = group['cn']
-    gid = group['gidNumber']
-    print('Members of {} ({}):'.format(cn, gid))
+    else:
+        group = groups[0]
+        cn = group['cn']
+        gid = group['gidNumber']
+        members = group['memberUid']
 
-    table = list()
+        print('Members of {} ({}):'.format(cn, gid))
 
-    for u in users:
-        row = list()
-        for attr in user_attrs:
-            row.append(u[attr])
-        table.append(row)
+        attrs = ['uid', 'uidNumber', 'gecos']
+        attrs.extend([v for k, v in OPTS.items() if args[k]])
 
-    print(tabulate.tabulate(table, tablefmt='simple', headers=user_attrs))
+        users = dphysldap.Users(ldap, attrs)
+        users.search(';'.join(members))
+        #users.sort('uid')
+        print(users)
 
 
 if __name__ == "__main__":
diff --git a/bin/showuser.py b/bin/showuser.py
new file mode 100755
index 0000000000000000000000000000000000000000..758cc29caa278d2639332920fb3a34c9749d211f
--- /dev/null
+++ b/bin/showuser.py
@@ -0,0 +1,60 @@
+#!/usr/bin/env python3
+
+import sys
+import argparse
+import collections
+import lib_path
+import lib
+import dphysldap
+
+OPTS = collections.OrderedDict()
+OPTS['access'] = 'accessRight'
+OPTS['blocked'] = 'blocked'
+OPTS['home'] = 'homeDirectory'
+OPTS['shell'] = 'loginShell'
+OPTS['mail'] = 'mail'
+OPTS['telephone'] = 'telephoneNumber'
+OPTS['gid'] = 'gidNumber'
+OPTS['cn'] = 'cn'
+OPTS['language'] = 'language'
+
+
+def get_args(opts=dict()):
+    parser = argparse.ArgumentParser(
+        add_help=False, description='Show user(s)')
+    parser.add_argument('user', help='The user name (uid), or wildcards')
+    for k, v in opts.items():
+        parser.add_argument('-' + k[:1], '--' + k, dest=k,
+                            action='store_const', const=True,
+                            help='Show ' + v)
+    parser.add_argument('--help', action='help',
+                        help='Show this help message and exit')
+    return vars(parser.parse_args())
+
+
+def main():
+    args = get_args(OPTS)
+
+    ldap = dphysldap.Ldap()
+
+    attrs = ['uid', 'uidNumber', 'gecos']
+    attrs.extend([v for k, v in OPTS.items() if args[k]])
+
+    users = dphysldap.Users(ldap, attrs)
+    users.search(args['user'])
+    #users.sort('uid')
+
+    if not users:
+        sys.exit('No userss found.')
+
+    if len(users) != 1:
+        print(users)
+
+    else:
+        user = users[0]
+        for attr in attrs:
+            print(': '.join([attr, str(user[attr])]))
+
+
+if __name__ == "__main__":
+    main()
diff --git a/lib/isg/dphysldap.py b/lib/isg/dphysldap.py
index cc3f8483890427d2a58815a8bb2ed3915d37f983..d8407187a3cafcb92ace83cacaaf1e7aef2b9ddf 100644
--- a/lib/isg/dphysldap.py
+++ b/lib/isg/dphysldap.py
@@ -1,11 +1,13 @@
 #!/usr/bin/env python3
 
 import ssl
-from collections.abc import Mapping
+import collections
 import lib_path
 import lib
 import ldap3
+import tabulate
 
+FMT = 'simple'
 SERVERS = ['phd-aa1.ethz.ch', 'phd-aa2.ethz.ch', 'phd-aa3.ethz.ch']
 BASE = 'dc=phys,dc=ethz,dc=ch'
 CA_CERTS = '/etc/ssl/certs/ca-certificates.crt'
@@ -28,7 +30,7 @@ class AttributeValue(list):
         return delimit.join([str(e) for e in self])
 
 
-class Entry(Mapping):
+class Entry(collections.abc.Mapping):
     """
     Abstraction class for LDAP Entry imitating dict
     """
@@ -49,6 +51,12 @@ class Entry(Mapping):
 
     def __len__(self):
         return len(self._storage)
+    
+    def __str__(self):
+        """
+        Modifies the "informal" string value of str(x) or print(x)
+        """
+        return '\n'.join([': '.join([k, str(v)]) for k, v in self.items() if v])
 
 
 class Ldap(object):
@@ -78,7 +86,7 @@ class Ldap(object):
         self.connection.start_tls()
         self.connection.bind()
         self.user_classes = ['posixAccount', 'dphysUser', 'inetOrgPerson', 'shadowAccount']
-        self.group_classes = ['posixGroup']
+        self.group_classes = ['posixGroup', 'dphysGroup']
         self.obj_user = None
         self.obj_group = None
 
@@ -105,3 +113,75 @@ class Ldap(object):
         if not self.obj_group:
             self.obj_group = ldap3.ObjectDef(self.group_classes, self.connection)
         return self.get_entries(self.obj_group, query=query, attributes=attributes)
+
+
+class Groups(list):
+    """
+    Abstraction class for Groups imitating list
+    """
+    def __init__(self, ldap, attrs, *args):
+        list.__init__(self, *args)
+        self._ldap = ldap
+        self._attrs = attrs
+
+    def __str__(self):
+        """
+        Modifies the "informal" string value of str(x) or print(x)
+        to return groups in tabulated form
+        """
+        headers = self._attrs
+        table = [[g[h] for h in headers] for g in self]
+        return tabulate.tabulate(table, tablefmt=FMT, headers=headers)
+
+    #def sort(self, attr):
+    #    self = sorted(self, key=lambda k: k[attr])
+
+    def search(self, cn):
+        """
+        Search example: `cn`, `cn*`, `*cn*`, `cn1;cn2`
+        """
+        query = 'cn: {0}'.format(cn)
+        self.clear()
+        self.extend(self._ldap.get_groups(query=query, attributes=self._attrs))
+
+    def members(self, index=None):
+        """
+        Returns list of members
+        """
+        if index == None:
+            members = set()
+            for group in self:
+                members.add(group['memberUid'])
+            return list(members)
+        else:
+            return self[index]['memberUid']
+
+
+class Users(list):
+    """
+    Abstraction class for Users imitating list
+    """
+    def __init__(self, ldap, attrs, *args):
+        list.__init__(self, *args)
+        self._ldap = ldap
+        self._attrs = attrs
+
+    def __str__(self):
+        """
+        Modifies the "informal" string value of str(x) or print(x)
+        to return users in tabulated form
+        """
+        headers = self._attrs
+        table = [[u[h] for h in headers] for u in self]
+        return tabulate.tabulate(table, tablefmt=FMT, headers=self._attrs)
+
+    #def sort(self, attr):
+    #    self = sorted(self, key=lambda d: d[attr].__str__())
+
+    def search(self, uid):
+        """
+        Search example: `uid`, `uid*`, `*uid*`, `uid1;uid2`
+        """
+        query = 'uid: {0}'.format(uid)
+        self.clear()
+        self.extend(self._ldap.get_users(query=query, attributes=self._attrs))