[prev in list] [next in list] [prev in thread] [next in thread] 

List:       kde-devel
Subject:    KShell: argument splitting and tilde expansion
From:       Oswald Buddenhagen <ossi () kde ! org>
Date:       2003-03-01 19:45:43
[Download RAW message or body]

moin,

due to the incredible demand for such functionality (there seem to be
roughly as many (more or less broken) implementations out there as kde
apps), i've implemented a class with a few static functions that cover
some basic ((posix) shell)/bash functionality.

tildeExpand is self-explaining, i think.

splitArgs performs bash-like argument splitting. the following
quotings prevent word-splitting:
- backslash.
- single quotes. everything between them is taken literally.
- double quotes. the backslash can be used to escape the double quote
  (and itself).
- bash-like $'' quoting. read the bash man page for details.
note that backslashes preceeding non-special chars are treated literally.
splitArgs can optionally perform tilde expansion, too. note that this is
the only place where this is allowed, as
- before splitting you don't know where words begin
- after splitting the quotes are gone, so you don't know whether tilde
  expansion was intended by the user
possible todo:
- simple substitutions ($var, ${var})
- complex substitutions (${var:-foo})
- command substitutions ($(echo hallo), `echo hallo`)
- pipes, redirections ($(echo foo|clobber 2>/dev/null))
- command lists ($(echo foo; echo bar))
- complex commands (conditions, loops, grouping, assignments, functions)
- smoke weaker shit to get the feet back on the ground

i provided joinArgs just in case somebody needs it, even though i didn't
find a place where it would be needed, yet.

comments welcome.

greetings

-- 
Hi! I'm a .signature virus! Copy me into your ~/.signature, please!
--
Chaos, panic, and disorder - my work here is done.

["kshell.h" (text/x-chdr)]

/*
    This file is part of the KDE libraries

    Copyright (c) 2003 Oswald Buddenhagen <ossi@kde.org>

    This library is free software; you can redistribute it and/or
    modify it under the terms of the GNU Library General Public
    License as published by the Free Software Foundation; either
    version 2 of the License, or (at your option) any later version.

    This library is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
    Library General Public License for more details.

    You should have received a copy of the GNU Library General Public License
    along with this library; see the file COPYING.LIB.  If not, write to
    the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
    Boston, MA 02111-1307, USA.
*/
#ifndef _KSHELL_H
#define _KSHELL_H

#include <qstring.h>
#include <qstringlist.h>

//struct KShellPrivate;

class KShell {

public:
    enum Options { NoOpts = 0, TildeExpand = 1 };
    static QStringList splitArgs( const QString &args, int flags = 0 );
    static QString joinArgs( const QStringList &args );
    static QString tildeExpand( const QString &fname );

private:
    static QString getHome( const QString &user );

    KShell();
    KShell(const KShell &);
    KShell &operator=(const KShell &);

//    KShellPrivate *d;
};


#endif /* _KSHELL_H */

["kshell.cpp" (text/x-c++src)]

/*
    This file is part of the KDE libraries

    Copyright (c) 2003 Oswald Buddenhagen <ossi@kde.org>

    This library is free software; you can redistribute it and/or
    modify it under the terms of the GNU Library General Public
    License as published by the Free Software Foundation; either
    version 2 of the License, or (at your option) any later version.

    This library is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
    Library General Public License for more details.

    You should have received a copy of the GNU Library General Public License
    along with this library; see the file COPYING.LIB.  If not, write to
    the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
    Boston, MA 02111-1307, USA.
*/

#include <kshell.h>

#include <qfile.h>

#include <stdlib.h>
#include <pwd.h>
#include <sys/types.h>

static int fromHex( QChar c )
{
    if (c >= '0' && c <= '9')
        return c - '0';
    else if (c >= 'A' && c <= 'F')
        return c - 'A' + 10;
    else if (c >= 'a' && c <= 'f')
        return c - 'a' + 10;
    return -1;
}

QStringList KShell::splitArgs( const QString &args, int flags )
{
    QStringList ret;

    for (uint pos = 0; ; ) {
        QChar c;
        do {
            if (pos >= args.length())
                return ret;
            c = args.unicode()[pos++];
        } while (c.isSpace());
        QString cret;
        if ((flags & TildeExpand) && c == '~') {
            uint opos = pos;
            for (; ; pos++) {
                if (pos >= args.length())
                    break;
                c = args.unicode()[pos];
                if (c == '/' || c.isSpace())
                    break;
                if (c == '\'' || c == '"' || c == '$' || c == '\\') {
                    pos = opos;
                    c = '~';
                    goto notilde;
                }
            }
            QString ccret = getHome( QConstString( args.unicode() + opos, pos - opos ).string() );
            if (ccret.isEmpty()) {
                pos = opos;
                c = '~';
                goto notilde;
            }
            if (pos >= args.length()) {
                ret += ccret;
                return ret;
            }
            pos++;
            if (c.isSpace()) {
                ret += ccret;
                continue;
            }
            cret = ccret;
        }
      notilde:
        do {
            if (c == '\'') {
                uint spos = pos;
                do {
                    if (pos >= args.length())
                        return QStringList();
                    c = args.unicode()[pos++];
                } while (c != '\'');
                cret += QConstString( args.unicode() + spos, pos - spos - 1 ).string();
            } else if (c == '"') {
                for (;;) {
                    if (pos >= args.length())
                        return QStringList();
                    c = args.unicode()[pos++];
                    if (c == '"')
                        break;
                    if (c == '\\') {
                        if (pos >= args.length())
                            return QStringList();
                        c = args.unicode()[pos++];
                        if (c != '"' && c != '\\')
                            cret += '\\';
                    }
                    cret += c;
                }
            } else if (c == '$' && args[pos] == '\'') {
                pos++;
                for (;;) {
                    if (pos >= args.length())
                        return QStringList();
                    c = args.unicode()[pos++];
                    if (c == '\'')
                        break;
                    if (c == '\\') {
                        if (pos >= args.length())
                            return QStringList();
                        c = args.unicode()[pos++];
                        switch (c) {
                        case 'a': cret += '\a'; break;
                        case 'b': cret += '\b'; break;
                        case 'e': cret += '\033'; break;
                        case 'f': cret += '\f'; break;
                        case 'n': cret += '\n'; break;
                        case 'r': cret += '\r'; break;
                        case 't': cret += '\t'; break;
                        case '\\': cret += '\\'; break;
                        case '\'': cret += '\''; break;
                        case 'c': cret += args[pos++] & 31; break;
                        case 'x':
                          {
                            int hv = fromHex( args[pos] );
                            if (hv < 0) {
                                cret += "\\x";
                            } else {
                                int hhv = fromHex( args[++pos] );
                                if (hhv > 0) {
                                    hv = hv * 16 + hhv;
                                    pos++;
                                }
                                cret += QChar( hv );
                            }
                            break;
                          }
                        default:
                            if (c >= '0' && c <= '7') {
                                int hv = c - '0';
                                for (int i = 0; i < 2; i++) {
                                    c = args[pos];
                                    if (c < '0' || c > '7')
                                        break;
                                    hv = hv * 8 + (c - '0');
                                    pos++;
                                }
                                cret += QChar( hv );
                            } else {
                                cret += '\\';
                                cret += c;
                            }
                            break;
                        }
                    } else
                        cret += c;
                }
            } else {
                if (c == '\\') {
                    if (pos >= args.length())
                        return QStringList();
                    c = args.unicode()[pos++];
                    if (!c.isSpace() && c != '\'' && c != '"' && c != '$' && c != '\\')
                        cret += '\\';
                }
                cret += c;
            }
            if (pos >= args.length())
                break;
            c = args.unicode()[pos++];
        } while (!c.isSpace());
        ret += cret;
    }
}

QString KShell::joinArgs( const QStringList &args )
{
    QString ret("");

    for (QStringList::ConstIterator it = args.begin(); it != args.end(); ++it) {
        ret += ret.isEmpty() ? "$'" : " $'";
        for (uint pos = 0; pos < (*it).length(); pos++) {
            int c = (*it).unicode()[pos];
            if (c < 32) {
                ret += '\\';
                switch (c) {
                case '\a': ret += 'a'; break;
                case '\b': ret += 'b'; break;
                case '\033': ret += 'e'; break;
                case '\f': ret += 'f'; break;
                case '\n': ret += 'n'; break;
                case '\r': ret += 'r'; break;
                case '\t': ret += 't'; break;
                case '\034': ret += "c|"; break;
                default: ret += 'c'; ret += c + '@'; break;
                }
            } else {
                if (c == '\'' || c == '\\')
                    ret += '\\';
                ret += c;
            }
        }
        ret += '\'';
    }
    return ret;
}

QString KShell::tildeExpand( const QString &fname )
{
    if (fname[0] == '~') {
        int pos = fname.find( '/' );
        if (pos < 0)
            return getHome( QConstString( fname.unicode() + 1, fname.length() - 1 ).string() );
        QString ret = getHome( QConstString( fname.unicode() + 1, fname.length() - pos - 1 ).string() );
        if (!ret.isNull())
            ret += QConstString( fname.unicode() + pos, fname.length() - pos ).string();
        return ret;
    }
    return fname;
}

QString KShell::getHome( const QString &user )
{
    if (user.isEmpty())
        return QFile::decodeName( getenv( "HOME" ) );
    struct passwd *pw = getpwnam( QFile::encodeName( user ) );
    if (!pw)
        return QString::null;
    return QFile::decodeName( pw->pw_dir );
}

["kshelltest.cpp" (text/x-c++src)]

#include <kshell.h>

#include <iostream>

int main()
{
#if 0
    std::cout << KShell::tildeExpand("~") << std::endl;
    std::cout << KShell::tildeExpand("~/sulli") << std::endl;
    std::cout << KShell::tildeExpand("~root") << std::endl;
    std::cout << KShell::tildeExpand("~root/sulli") << std::endl;
    std::cout << KShell::tildeExpand("~sulli") << std::endl;
#endif
#if 0
    QStringList lst;
    lst << "this" << "is" << "text";
    std::cout << KShell::joinArgs(lst) << std::endl;
#endif
    std::cout << KShell::joinArgs(KShell::splitArgs("\"~sulli\" 'text' 'jo'\"jo\" $'crap' \
$'\\\\\\'\\ca\\e\\x21' ha\\ lo ")) << std::endl;  std::cout << \
KShell::joinArgs(KShell::splitArgs("\"~sulli\" 'text'", KShell::TildeExpand)) << std::endl;  std::cout << \
KShell::joinArgs(KShell::splitArgs("~\"sulli\" 'text'", KShell::TildeExpand)) << std::endl;  std::cout << \
KShell::joinArgs(KShell::splitArgs("~/\"sulli\" 'text'", KShell::TildeExpand)) << std::endl;  std::cout \
<< KShell::joinArgs(KShell::splitArgs("~ 'text' ~", KShell::TildeExpand)) << std::endl;  std::cout << \
KShell::joinArgs(KShell::splitArgs("~sulli ~root", KShell::TildeExpand)) << std::endl; }



CFLAGS := -O2 -march=$(shell uname -m) -g -W -Wall
CXXFLAGS := $(CFLAGS) -fno-exceptions

QTDIR := /usr/local/kde
KSHELLDIR := .

test: kshelltest
	./kshelltest

kshelltest: kshelltest.o kshell.o
	$(CXX) -o $@ $^ -L$(QTDIR)/lib -lqt-mt

kshelltest.o: kshelltest.cpp $(KSHELLDIR)/kshell.h
	$(CXX) $(CXXFLAGS) -o $@ -c $< -I$(QTDIR)/include -I$(KSHELLDIR)

kshell.o: $(KSHELLDIR)/kshell.cpp $(KSHELLDIR)/kshell.h
	$(CXX) $(CXXFLAGS) -o $@ -c $< -I$(QTDIR)/include -I$(KSHELLDIR)

clean:
	rm -f *.o *.s kshelltest

>> Visit http://mail.kde.org/mailman/listinfo/kde-devel#unsub to unsubscribe <<

[prev in list] [next in list] [prev in thread] [next in thread] 

Configure | About | News | Add a list | Sponsored by KoreLogic