php.de

Zurück   php.de > Webentwicklung > PHP-Fortgeschrittene

PHP-Fortgeschrittene Arbeiten mit PHP ohne Einschränkungen

Antwort
 
LinkBack Themen-Optionen Thema bewerten
Alt 31.03.2011, 16:01  
Erfahrener Benutzer
 
Registriert seit: 21.07.2003
Beiträge: 340
PHP-Kenntnisse:
Fortgeschritten
Simbo
Standard Parameterstring splitten unter Berücksichtigung von Quotes und Escaping

Hallo zusammen,...

Ich möchte einen String splitten, ähnlich dem Verhalten des Parameterparsing einer Linux-Shell:
Ein oder mehrere Vorkommen von Whitespace trennen die einzelnen Substrings. Es sei denn, das Whitespace-Zeichen ist escaped oder der Bereich, in dem es vorkommt, steht in einfachen oder doppelten Anführungszeichen. Escaping von Anführungszeichen soll auch beachtet werden.

Ein Beispiel...
Input:
Code:
a 'b'  "c d" e"\" f" ' g" ' '\'h' i\ j k" l"m n"\""
Output:
Code:
Array
(
    [0] => a
    [1] => 'b'
    [2] => "c d"
    [3] => e"\" f"
    [4] => ' g" '
    [5] => '\'h'
    [6] => i\ j
    [7] => k" l"m
    [8] => n"\""
)
Ich hab das ganze mal nach der "Hausfrauenmethode" umgesetzt, was bisher ganz gut zu funktionieren scheint:
PHP-Code:
$ps = <<< eod
a 'b'  "c d" e"\" f" ' g" ' '\'h' i\ j k" l"m n"\""
eod;

$chars str_split(trim($ps),1);
$escape false;
$quotes false;
$params = array();
$p '';
while( 
count($chars)>) {
    
$c array_shift($chars);
    if( !
$quotes && !$escape && preg_match('/^\s$/',$c) ){
        if(!empty(
$p))
            
array_push($params,$p);
        
$p '';
        
$escape false;
        continue;
    }
    if( 
$c=='\'' || $c=='"' )
        
$quotes $quotes==$c && !$escape false : ( !$quotes $c $quotes );
    
$escape $c=='\\' && !$escape true false;
    
$p .= $c;
}
array_push($params,$p);
print_r($params); 
Jetzt hätte ich das ganze aber gern kürzer (und wahrscheinlich auch schneller) per RegExp...

Also hab ich folgendes gebastelt:
PHP-Code:
preg_match_all'/(^|\s)(([^"\'\s][^\s]*)|((["\'])([^\5]+?)\5))/m'$ps$matches );
print_r($matches[2]); 
erzeugt folgende Ausgabe:
Code:
Array
(
    [0] => a
    [1] => 'b'
    [2] => "c d"
    [3] => e"\"
    [4] => f"
    [5] => ' g" '
    [6] => '\'
    [7] => i\
    [8] => j
    [9] => k"
    [10] => l"m
    [11] => n"\""
)
Wie man sieht wird Escaping bei Anführungszeichen und Whitespace nicht beachtet.
Also... weiß jemand wie ich Escaping in den RegExp einbaue?
Oder hat vielleicht sonst wer einen Vorschlag wie man das besser machen könnte?

Schönen Gruß

Simbo
__________________
simbo.de

Geändert von Simbo (31.03.2011 um 16:05 Uhr).
Simbo ist offline   Mit Zitat antworten
Sponsor Mitteilung
PHP Code Flüsterer

Registriert seit: 21.08.2005
Beiträge: 4682
PHP-Kenntnisse:
Fortgeschritten

Alt 31.03.2011, 16:08  
Moderator¹
 
Registriert seit: 28.03.2010
Beiträge: 7.470
PHP-Kenntnisse:
Fortgeschritten
ChrisB ist ein wunderbarer AnblickChrisB ist ein wunderbarer AnblickChrisB ist ein wunderbarer AnblickChrisB ist ein wunderbarer AnblickChrisB ist ein wunderbarer AnblickChrisB ist ein wunderbarer AnblickChrisB ist ein wunderbarer Anblick
Standard

Zitat:
Zitat von Simbo Beitrag anzeigen
Wie man sieht wird Escaping bei Anführungszeichen und Whitespace nicht beachtet.
Warum sollte es auch?

Zitat:
Also... weiß jemand wie ich Escaping in den RegExp einbaue?
Lookbehind assertions könnten in diesem Punkt helfen.
__________________
RGB is totally confusing - I mean, at least #C0FFEE should be brown, right?
ChrisB ist offline   Mit Zitat antworten
Alt 31.03.2011, 16:35  
moderatives Dielektrikum
 
Benutzerbild von nikosch
 
Registriert seit: 21.05.2008
Beiträge: 35.987
PHP-Kenntnisse:
Fortgeschritten
nikosch hat eine strahlende Zukunftnikosch hat eine strahlende Zukunftnikosch hat eine strahlende Zukunftnikosch hat eine strahlende Zukunftnikosch hat eine strahlende Zukunftnikosch hat eine strahlende Zukunftnikosch hat eine strahlende Zukunftnikosch hat eine strahlende Zukunftnikosch hat eine strahlende Zukunftnikosch hat eine strahlende Zukunftnikosch hat eine strahlende Zukunft
Standard

Nicht ganz sicher, ob das schon alle Escapesituationen abdeckt. Aber etwa so gehts:
PHP-Code:
$pattern = <<<pattern

#(?:^|\s+)((?:(["])['\w\s]+\\2|(['])["\w\s]+\\3|(?:\\\[\040])|(?:\\\)|(?:\\')|(?:\\")|[\w]+)*)#

pattern;

$pattern trim ($pattern); 
[edit] Stimmt definitiv noch nicht, aber vielleicht nützt Dir der Ansatz was.
__________________
--
One pixel is still too big. Please make it smaller. ASAP.

Initiative Mittelstand.
Die wichtigste Gestaltungsregel im Screendesign ist Pi mal Daumen des Arbeitgebers.
--
nikosch ist offline   Mit Zitat antworten
Alt 31.03.2011, 18:30  
Erfahrener Benutzer
 
Registriert seit: 21.07.2003
Beiträge: 340
PHP-Kenntnisse:
Fortgeschritten
Simbo
Standard

Super nikosch, das hilft mir schon mal sehr weiter.
Ich hab das jetzt mal insofern umgebaut, dass alle Zeichen außer den jeweiligen Anführungszeichen und Escapezeichen beachtet werden.
Das funktioniert bei meinem Teststring fast perfekt.

Manko:
Falls ein in Anführungszeichen stehender Bereich nicht wieder durch das jeweilige Anführungszeichen geschlossen wird, soll er bis zum Ende des Strings gelesen werden.

PHP-Code:
$ps = <<< eod
a   'b'  "c
 d" e"\" f" ' gæ" ' '\'h' i\ j k" l"m n"\"" a" öd
eod;

$pattern = <<<pattern
#(?:^|\s+)((?:(["])[^"\\\]+\\2|(['])[^'\\\]+\\3|(?:\\\[\040])|(?:\\\)|(?:\\')|(?:\\")|[^\s\\\'"]+)*)#
pattern;
preg_match_all$pattern$ps$matches );

header('Content-Type: text/plain;charset=utf-8');
print_r($matches[1]); 
erzeugt folgende Ausgabe:
Code:
Array
(
    [0] => a
    [1] => 'b'
    [2] => "c
 d"
    [3] => e"\" f"
    [4] => ' gæ" '
    [5] => '\'h'
    [6] => i\ j
    [7] => k" l"m
    [8] => n"\"" a"
    [9] => öd
)
Korrekt wäre:
Code:
    [8] => n"\""
    [9] => a" öd
[edit]
Denkt ihr, es lohnt sich überhaupt, das ganze als RegExp zu bauen oder sollte ich besser bei der oben beschriebenen Methode bleiben?
Die macht ja auch nichts anderes als jedes Zeichen zu betrachten. Wäre die RegExp-Methode von der Performance her wirklich besser?

[edit2]
nochmal überarbeitet... immer noch fehlerbehaftet...
PHP-Code:
$pattern = <<<pattern
#(?:^|\s+)((?:(["])[^"\\\]*?(\\2|$)|(['])[^'\\\]*?(\\4|$)|(?:\\\[\040])|(?:\\')|(?:\\")|(?:\\\)|[^\s\\\'"]+)*)#
pattern; 
__________________
simbo.de

Geändert von Simbo (31.03.2011 um 20:45 Uhr).
Simbo ist offline   Mit Zitat antworten
Alt 31.03.2011, 20:16  
Erfahrener Benutzer
 
Benutzerbild von mermshaus
 
Registriert seit: 14.06.2009
Beiträge: 1.731
PHP-Kenntnisse:
Fortgeschritten
mermshaus kann auf vieles stolz seinmermshaus kann auf vieles stolz seinmermshaus kann auf vieles stolz seinmermshaus kann auf vieles stolz seinmermshaus kann auf vieles stolz seinmermshaus kann auf vieles stolz seinmermshaus kann auf vieles stolz seinmermshaus kann auf vieles stolz seinmermshaus kann auf vieles stolz sein
Standard

Kommentare zu Qualität oder Performance kann ich leider keine abgeben.

Ich habe kurz gebastelt, und der Code lief überraschenderweise beim ersten Ausführen, weshalb ich nicht zu sehr über mögliche Fehler nachgedacht habe.

Lasse den einfach mal da, auch wenn er vielleicht nicht benötigt wird.

Code:
<?php

function getArgsArray($input)
{
    $args = array();

    $argBuffer = '';

    $isInString         = false;
    $isInEscapeSequence = false;

    $activeStringDelimiter = '';

    $inputLength = mb_strlen($input);

    for ($i = 0; $i < $inputLength; $i++) {
        $activeChar = mb_substr($input, $i, 1);

        switch (true) {
            // Space
            case 1 === preg_match('/ /', $activeChar):                
                if ($isInString) {
                    $argBuffer .= $activeChar;
                } else if ($isInEscapeSequence) {
                    $isInEscapeSequence = false;
                    $argBuffer .= $activeChar;
                } else {
                    // Complete argument read,
                    // push buffer to list (if there is content),
                    // flush buffer
                    if ($argBuffer !== '') {
                        $args[] = $argBuffer;
                        $argBuffer = '';
                    }
                }
                break;

            // String delimiter
            case 1 === preg_match('/[\'"]/', $activeChar):                
                if ($isInEscapeSequence) {
                    $isInEscapeSequence = false;
                    $argBuffer .= $activeChar;
                } else if ($isInString) {
                    if ($activeStringDelimiter === $activeChar) {
                        $isInString = false;
                    }
                    $argBuffer .= $activeChar;
                } else {
                    $isInString = true;
                    $activeStringDelimiter = $activeChar;
                    $argBuffer .= $activeChar;
                }
                break;

            // Escape char
            case 1 === preg_match('/\x5C/', $activeChar):                
                if ($isInEscapeSequence) {
                    $isInEscapeSequence = false;
                    $argBuffer .= $activeChar;
                } else {
                    $isInEscapeSequence = true;
                    $argBuffer .= $activeChar;
                }
                break;

            // Other char
            default:                
                if ($isInEscapeSequence) {
                    $isInEscapeSequence = false;
                    $argBuffer .= $activeChar;
                } else {
                    $argBuffer .= $activeChar;
                }
                break;
        }
    }

    if ($argBuffer !== '') {
        $args[] = $argBuffer;
    }

    return $args;
}

$test = <<<'EOT'
a 'b'  "c d" e"\" f" ' g" ' '\'h' i\ j k" l"m n"\""
EOT;

$args = getArgsArray($test);

header('content-type: text/plain');

print_r($args);
Sicherlich müsste weder mit mb_ iteriert werden noch per preg_ verglichen werden, aber gut.
__________________
Blog | Buch | Kaloa
mermshaus ist offline   Mit Zitat antworten
Alt 31.03.2011, 21:26  
moderatives Dielektrikum
 
Benutzerbild von nikosch
 
Registriert seit: 21.05.2008
Beiträge: 35.987
PHP-Kenntnisse:
Fortgeschritten
nikosch hat eine strahlende Zukunftnikosch hat eine strahlende Zukunftnikosch hat eine strahlende Zukunftnikosch hat eine strahlende Zukunftnikosch hat eine strahlende Zukunftnikosch hat eine strahlende Zukunftnikosch hat eine strahlende Zukunftnikosch hat eine strahlende Zukunftnikosch hat eine strahlende Zukunftnikosch hat eine strahlende Zukunftnikosch hat eine strahlende Zukunft
Standard

Zeichenweise ist immer besser IMHO, allerdings sind die Problematiken rund um Multibytestrings immer etwas schwer einzuschätzen.
__________________
--
One pixel is still too big. Please make it smaller. ASAP.

Initiative Mittelstand.
Die wichtigste Gestaltungsregel im Screendesign ist Pi mal Daumen des Arbeitgebers.
--
nikosch ist offline   Mit Zitat antworten
Alt 31.03.2011, 21:50  
Erfahrener Benutzer
 
Benutzerbild von mermshaus
 
Registriert seit: 14.06.2009
Beiträge: 1.731
PHP-Kenntnisse:
Fortgeschritten
mermshaus kann auf vieles stolz seinmermshaus kann auf vieles stolz seinmermshaus kann auf vieles stolz seinmermshaus kann auf vieles stolz seinmermshaus kann auf vieles stolz seinmermshaus kann auf vieles stolz seinmermshaus kann auf vieles stolz seinmermshaus kann auf vieles stolz seinmermshaus kann auf vieles stolz sein
Standard

Code:
    $inputLength = mb_strlen($input);

    for ($i = 0; $i < $inputLength; $i++) {
        $activeChar = mb_substr($input, $i, 1);
Das ist richtig richtig langsam, da mb_substr meiner Erfahrung nach nicht speichert, an welchem Byte der Character $i-1 liegt. Beim $i-ten Durchlauf muss von Stringanfang an wieder alles durchgerechnet werden (Multibyte-Chars haben unterschiedliche Längen).

Man müsste mal in sich gehen und darüber nachdenken, ob hier nicht byte-weises Vorgehen (also die normalen Stringfunktionen) ausreicht. Ich gehe stark davon aus, dass es ausreichen wird, da alle entscheidenden Zeichen ASCII sind.
__________________
Blog | Buch | Kaloa

Geändert von mermshaus (31.03.2011 um 21:54 Uhr).
mermshaus ist offline   Mit Zitat antworten
Alt 04.04.2011, 12:40  
Erfahrener Benutzer
 
Benutzerbild von fireweasel
 
Registriert seit: 20.03.2010
Beiträge: 474
PHP-Kenntnisse:
Fortgeschritten
fireweasel ist ein Lichtblickfireweasel ist ein Lichtblickfireweasel ist ein Lichtblickfireweasel ist ein Lichtblickfireweasel ist ein Lichtblick
fireweasel eine Nachricht über ICQ schicken fireweasel eine Nachricht über AIM schicken fireweasel eine Nachricht über Yahoo! schicken fireweasel eine Nachricht über Skype™ schicken
Standard

Zitat:
Zitat von Simbo Beitrag anzeigen
...
Falls ein in Anführungszeichen stehender Bereich nicht wieder durch das jeweilige Anführungszeichen geschlossen wird, soll er bis zum Ende des Strings gelesen werden.
Deine "Spezifikation" ist noch an anderen Stellen unklar. So ist es (derzeit) nicht möglich, einen Backslash als normales Zeichen vor das Ende eines "ge-quoteten" Bereichs oder vor ein Leerzeichen außerhalb eines solchen Bereiches zu setzen. Es fehlt also noch die Escaping-Sequenz für Backslashes als normale Zeichen.

Zitat:
Denkt ihr, es lohnt sich überhaupt, das ganze als RegExp zu bauen oder sollte ich besser bei der oben beschriebenen Methode bleiben?
Die RegEx-Variante funktioniert. Ob sie Vorteile gegenüber der Einzelzeichenabfrage hat, hängt von den Einsatzbedingungen ab.

Zitat:
Die macht ja auch nichts anderes als jedes Zeichen zu betrachten. Wäre die RegExp-Methode von der Performance her wirklich besser?
Das kannst du messen. Aber bitte an praxisrelevanten Parameterstrings und nicht den Einzelbuchstaben-Sammlungen deiner beiden Test-Strings.

Zitat:
... nochmal überarbeitet... immer noch fehlerbehaftet ...
Dann hier mal ein RegEx, der (mit deinen beiden Test-Strings) funktioniert. Zuerst als übersichtlicher Dreizeiler:
PHP-Code:
$pcre '/
(?(DEFINE)(?<quot> (?:"(?:\x5C"|[^"])+(?:"|\z)|\'(?:\x5C\'|[^\'])+(?:\'|\z)) ))
(?(DEFINE)(?<nonq> (?:\x5C\s|[\x21\x23-\x26\x28-\x9F\xA1-\xFF])+ ))
(?:(?&quot)(?&nonq)?|(?&nonq)(?:(?&quot)(?&nonq)?)?)
/x'

Der erzeugt mit preg_match_all($pcre, ...) leider vier unnötige Leer-Arrays. Das sind die "Subroutinen", die ins Leere greifen (einmal als nummeriertes Subpattern und einmal in der "named"-Variante).

Die andere Variante macht fast das Gleiche, nur werden hier die Teilmuster jedesmal neu deklariert. Das ist weniger gut wartbar, wie ich beim "Entwickeln" des Ganzen feststellen musste. Dafür werden keine unnötigen Arrays erzeugt.

Außerdem ist der RegEx überraschend einfach konstruiert: Es kommt mit einer Ausnahme ohne Assertions aus. Lediglich das String-Ende wird mit (?:\'|\z) und (?:"|\z) abgefragt. Sonst wird nur einfaches Pattern-Matching verwendet.

PHP-Code:
$pcre '/(?:
    # q, qn
        (?:"(?:\x5C"|[^"])+(?:"|\z)|\'(?:\x5C\'|[^\'])+(?:\'|\z))
        (?:(?:\x5C\s|[\x21\x23-\x26\x28-\x9F\xA1-\xFF])+)?
    |
    # n, nq, nqn
        (?:\x5C\s|[\x21\x23-\x26\x28-\x9F\xA1-\xFF])+
        (?:
            (?:"(?:\x5C"|[^"])+(?:"|\z)|\'(?:\x5C\'|[^\'])+(?:\'|\z))
            (?:(?:\x5C\s|[\x21\x23-\x26\x28-\x9F\xA1-\xFF])+)?
        )?
)/x'

Die Kommentare mit n, nq, nqn, q und qn sollen die Muster-Kombinationen verdeutlichen, nach denen der RegEx sucht: Das n steht für "non-quoted" und das q für "quoted", also für den jeweiligen zu erfassenden Teilbereich eines übergebenen Parameters.

Geändert von fireweasel (04.04.2011 um 15:55 Uhr). Grund: ein falsches \x79 durch ein \x9F ersetzt
fireweasel ist offline   Mit Zitat antworten
Alt 04.04.2011, 14:20  
moderatives Dielektrikum
 
Benutzerbild von nikosch
 
Registriert seit: 21.05.2008
Beiträge: 35.987
PHP-Kenntnisse:
Fortgeschritten
nikosch hat eine strahlende Zukunftnikosch hat eine strahlende Zukunftnikosch hat eine strahlende Zukunftnikosch hat eine strahlende Zukunftnikosch hat eine strahlende Zukunftnikosch hat eine strahlende Zukunftnikosch hat eine strahlende Zukunftnikosch hat eine strahlende Zukunftnikosch hat eine strahlende Zukunftnikosch hat eine strahlende Zukunftnikosch hat eine strahlende Zukunft
Standard

Zitat:
(?(DEFINE)(?<quot> ))
Wow, man lernt echt nie aus, bei regulären Ausdrücken. Sehr schöne Lösung, fireweasel (auch wenn ich jetzt noch nicht probiert habe, ob es funktioniert)..
__________________
--
One pixel is still too big. Please make it smaller. ASAP.

Initiative Mittelstand.
Die wichtigste Gestaltungsregel im Screendesign ist Pi mal Daumen des Arbeitgebers.
--
nikosch ist offline   Mit Zitat antworten
Alt 04.04.2011, 15:40  
Erfahrener Benutzer
 
Benutzerbild von mermshaus
 
Registriert seit: 14.06.2009
Beiträge: 1.731
PHP-Kenntnisse:
Fortgeschritten
mermshaus kann auf vieles stolz seinmermshaus kann auf vieles stolz seinmermshaus kann auf vieles stolz seinmermshaus kann auf vieles stolz seinmermshaus kann auf vieles stolz seinmermshaus kann auf vieles stolz seinmermshaus kann auf vieles stolz seinmermshaus kann auf vieles stolz seinmermshaus kann auf vieles stolz sein
Standard

Für den Demostring scheint die Lösung zu funktionieren. (War wohl von auszugehen. ) Offenbar ist sie auch schneller (mindestens! Faktor 5) als die Nicht-Regex-Variante, selbst wenn ich dort die mb_- und preg_-Funktionen ausbaue und nur noch Kontrollfluss-Konstrukte, Vergleiche und Zuweisungen nutze.

Wobei ich da mal wieder nicht beurteilen kann, was PCRE intern so treibt. Ich bin schon dazu übergegangen, nur einen einzelnen Aufruf zu testen, da PHP/PCRE einmal kompilierte Patterns cachet, aber – na ja – alles nur gefährliches Halbwissen.

Scheint wieder mal die Faustregel zu bestätigen, Operationen – selbst relativ kleine – eher nicht in PHP-Code durchzuführen.
__________________
Blog | Buch | Kaloa

Geändert von mermshaus (04.04.2011 um 15:48 Uhr).
mermshaus ist offline   Mit Zitat antworten
Antwort


Themen-Optionen
Thema bewerten
Thema bewerten:

Forumregeln
Es ist dir nicht erlaubt, neue Themen zu verfassen.
Es ist dir nicht erlaubt, auf Beiträge zu antworten.
Es ist dir nicht erlaubt, Anhänge hochzuladen.
Es ist dir nicht erlaubt, deine Beiträge zu bearbeiten.

BB-Code ist an.
Smileys sind an.
[IMG] Code ist an.
HTML-Code ist aus.
Trackbacks are an
Pingbacks are an
Refbacks are an
Gehe zu

Besucher kamen über folgende Suchanfragen bei Google auf diese Seite
php parameter string, php parameterstring, php string zerlegen leerzeichen anführungszeichen, bash string aufteilen, split string außer bei anführungszeichen, c join split strings escape, bash split quoted string, php parameter = string, mermshaus shell, string separieren shell, bash url zerlegen, php parameterstring to array, string innerhalb anführungszeichen bash, zeichenkette parameter aufspalten, g php escape anführungszeichen, split string quoted escape, php escaping <, php parameter string leerzeichen, javascript anführungszeichen escape, escaping in php

Alle Zeitangaben in WEZ +2. Es ist jetzt 01:32 Uhr.




Powered by vBulletin® Version 3.7.2 (Deutsch)
Copyright ©2000 - 2012, Jelsoft Enterprises Ltd.
Search Engine Optimization by vBSEO 3.2.0
Aprilia-Forum, Aquaristik-Forum, Liebeskummer-Forum, Zierfisch-Forum, Geizkragen-Forum