Wie kann ich in Perl genau prüfen, ob eine $ -Variable definiert ist und eine Zeichenfolge ungleich Null enthält?

82

Ich verwende derzeit das folgende Perl, um zu überprüfen, ob eine Variable definiert ist und Text enthält. Ich muss definedzuerst prüfen , um eine Warnung "Nicht initialisierter Wert" zu vermeiden:

if (defined $name && length $name > 0) {
    # do something with $name
}

Gibt es eine bessere (vermutlich prägnantere) Möglichkeit, dies zu schreiben?

Jessica
quelle

Antworten:

76

Sie sehen häufig die Überprüfung auf Definiertheit, damit Sie sich nicht mit der Warnung für die Verwendung eines undef-Werts befassen müssen (und in Perl 5.10 wird die fehlerhafte Variable angezeigt):

 Use of uninitialized value $name in ...

Um diese Warnung zu umgehen, kommen die Leute auf alle Arten von Code, und dieser Code scheint eher ein wichtiger Teil der Lösung zu sein als der Kaugummi und das Klebeband, die er ist. Manchmal ist es besser zu zeigen, was Sie tun, indem Sie die Warnung, die Sie vermeiden möchten, explizit deaktivieren:

 {
 no warnings 'uninitialized';

 if( length $name ) {
      ...
      }
 }

In anderen Fällen verwenden Sie anstelle der Daten einen Nullwert. Mit dem definierten oder Operator von Perl 5.10 können Sie lengthanstelle der Variablen, die die Warnung auslöst , eine explizite leere Zeichenfolge (definiert und Länge von Null) zurückgeben:

 use 5.010;

 if( length( $name // '' ) ) {
      ...
      }

In Perl 5.12 ist dies etwas einfacher, da lengthbei einem undefinierten Wert auch undefiniert zurückgegeben wird . Das mag ein bisschen albern erscheinen, aber das freut den Mathematiker, den ich vielleicht sein wollte. Das gibt keine Warnung aus, weshalb diese Frage existiert.

use 5.012;
use warnings;

my $name;

if( length $name ) { # no warning
    ...
    }
brian d foy
quelle
4
In Version 5.12 und höher wird length undefundef zurückgegeben, anstatt zu warnen und 0 zurückzugeben. Im booleschen Kontext ist undef genauso falsch wie 0. Wenn Sie also auf Version 5.12 oder höher abzielen, können Sie einfach schreibenif (length $name) { ... }
rjbs
24

Wie mobrule angibt, können Sie stattdessen Folgendes für eine kleine Ersparnis verwenden:

if (defined $name && $name ne '') {
    # do something with $name
}

Sie könnten den definierten Scheck fallen lassen und etwas noch kürzeres bekommen, z.

if ($name ne '') {
    # do something with $name
}

Aber in dem Fall, in dem $namenicht definiert ist, obwohl der Logikfluss genau wie beabsichtigt funktioniert warnings, erhalten Sie die folgende Ermahnung , wenn Sie verwenden (und Sie sollten es sein):

Verwendung eines nicht initialisierten Wertes in der Zeichenfolge ne

Wenn es also eine Chance gibt, die $namemöglicherweise nicht definiert ist, müssen Sie in erster Linie die Definiertheit überprüfen, um diese Warnung zu vermeiden. Wie Sinan Ünür hervorhebt, können Sie Scalar :: MoreUtils verwenden , um Code, der genau das tut (prüft auf Definiertheit, prüft dann auf Nulllänge) , sofort über die folgende empty()Methode abzurufen :

use Scalar::MoreUtils qw(empty);
if(not empty($name)) {
    # do something with $name 
}
Adam Bellaire
quelle
17

Erstens, da lengthimmer eine nicht negative Zahl zurückgegeben wird,

if ( length $name )

und

if ( length $name > 0 )

sind gleichwertig.

Wenn Sie einen undefinierten Wert durch eine leere Zeichenfolge ersetzen können, können Sie den //=Operator von Perl 5.10 verwenden , der die RHS der LHS zuweist, sofern die LHS nicht definiert ist:

#!/usr/bin/perl

use feature qw( say );
use strict; use warnings;

my $name;

say 'nonempty' if length($name //= '');
say "'$name'";

Beachten Sie das Fehlen von Warnungen zu einer nicht initialisierten Variablen, da $namedie leere Zeichenfolge zugewiesen wird, wenn sie nicht definiert ist.

Wenn Sie jedoch nicht von der Installation von 5.10 abhängig sein möchten, verwenden Sie die von Scalar :: MoreUtils bereitgestellten Funktionen . Zum Beispiel kann das Obige wie folgt geschrieben werden:

#!/usr/bin/perl

use strict; use warnings;

use Scalar::MoreUtils qw( define );

my $name;

print "nonempty\n" if length($name = define $name);
print "'$name'\n";

Wenn Sie nicht überfallen möchten $name, verwenden Sie default.

Sinan Ünür
quelle
+1 für "// =" Erwähnung (woher wusste ich, dass das Sinans Antwort wäre :)
DVK
4
Ich würde // = in diesem Fall nicht verwenden, da es die Daten als Nebeneffekt ändert. Verwenden Sie stattdessen die etwas kürzere length( $name // '' ).
Brian D Foy
@brian d'foy Ich denke, es hängt davon ab, was in der Funktion gemacht wird.
Sinan Ünür
+1 Das //und//= Operatoren sind möglicherweise die nützlichsten spezialisierten Operatoren, die es gibt.
Chris Lutz
1
Wie @rjbs in meiner Antwort hervorhob, kann mit v5.12 und höher lengthjetzt etwas zurückgegeben werden, das keine Zahl ist (aber nicht NaN;)
brian d foy
6

In Fällen, in denen es mir egal ist, ob die Variable undefgleich oder gleich ist, fasse ''ich sie normalerweise wie folgt zusammen:

$name = "" unless defined $name;
if($name ne '') {
  # do something with $name
}
Gaurav
quelle
In Perl 5.10 kann dies verkürzt werden, $name //= "";was genau das ist, was Sinan gepostet hat.
Chris Lutz
Und selbst wenn Sie nicht Perl 5.10 haben, können Sie trotzdem schreiben$name ||= "";
RET
1
@RET: Sie können das || nicht verwenden Operator hier, da er die Zeichenfolge '0' durch '' ersetzt. Sie müssen überprüfen, ob es definiert ist, nicht wahr.
Brian D Foy
Chris, RET: Ja, ich weiß. Ich habe speziell versucht vorzuschlagen, dass Jessica, wenn sie sich nicht mit dem Unterschied zwischen undefund befasst "", einfach einen zum anderen wechseln und einen einzigen Test verwenden sollte. Dies funktioniert im allgemeinen Fall nicht, für den die anderen veröffentlichten Lösungen viel besser sind, führt aber in diesem speziellen Fall zu ordentlichem Code. Sollte ich meine Antwort umformulieren, um dies klarer zu machen?
Gaurav
1

Du könntest sagen

 $name ne ""

anstatt

 length $name > 0
Mob
quelle
7
Dies gibt Ihnen weiterhin eine Warnung. Der Grund, warum Personen zuerst die Definitivität überprüfen, besteht darin, die Warnung "Nicht initialisierter Wert" zu vermeiden.
Brian D Foy
1

Es ist nicht immer möglich, sich wiederholende Dinge auf einfache und elegante Weise zu tun.

Tun Sie einfach das, was Sie immer tun, wenn Sie gemeinsamen Code haben, der in vielen Projekten repliziert wird:

Suchen Sie nach CPAN, möglicherweise hat jemand bereits den Code für Sie. Für diese Ausgabe habe ich Scalar :: MoreUtils gefunden .

Wenn Sie in CPAN nichts finden, was Ihnen gefällt, erstellen Sie ein Modul und fügen Sie den Code in eine Unterroutine ein:

package My::String::Util;
use strict;
use warnings;
our @ISA = qw( Exporter );
our @EXPORT = ();
our @EXPORT_OK = qw( is_nonempty);

use Carp  qw(croak);

sub is_nonempty ($) {
    croak "is_nonempty() requires an argument" 
        unless @_ == 1;

    no warnings 'uninitialized';

    return( defined $_[0] and length $_[0] != 0 );
}

1;

=head1 BOILERPLATE POD

blah blah blah

=head3 is_nonempty

Returns true if the argument is defined and has non-zero length.    

More boilerplate POD.

=cut

Dann nennen Sie es in Ihrem Code:

use My::String::Util qw( is_nonempty );

if ( is_nonempty $name ) {
    # do something with $name
}

Wenn Sie Prototypen ablehnen und keine Einwände gegen die zusätzlichen Parens erheben, überspringen Sie den Prototyp im Modul und nennen Sie ihn wie folgt : is_nonempty($name).

daotoad
quelle
2
Ist das nicht so, als würde man eine Fliege mit einem Hammer töten?
Zoran Simic
4
@Zoran Nein. Wenn Sie Code wie diesen herausrechnen, ist es besser, wenn ein komplizierter Zustand an vielen verschiedenen Stellen repliziert wird. Das wäre, als würde man einen Elefanten mit Nadelstichen töten. @daotoad: Ich denke, Sie sollten Ihre Antwort verkürzen, um die Verwendung von zu betonen Scalar::MoreUtils.
Sinan Ünür
@Zoran: Scalar :: MoreUtils ist ein sehr leichtes Modul ohne Abhängigkeiten. Die Semantik ist ebenfalls bekannt. Wenn Sie nicht gegen CPAN allergisch sind, gibt es nicht viel Grund, die Verwendung zu vermeiden.
Adam Bellaire
1
@ Chris Lutz, ja, ich sollte nicht. Aber Prototypen sind halb kaputt - es gibt einfache Möglichkeiten, die Durchsetzung von Prototypen zu brechen. Zum Beispiel fördern beschissene und / oder veraltete Tutorials weiterhin die Verwendung des &Siegels beim Aufrufen von Funktionen. Daher verlasse ich mich nicht auf Prototypen, um die ganze Arbeit zu erledigen. Ich nehme an, ich könnte der Fehlermeldung "hinzufügen und die Verwendung von & sigil bei Unteraufrufen beenden, es sei denn, Sie meinen es wirklich so".
Daotoad
1
Es ist einfacher, sich Prototypen als Hinweise für den Perl-Compiler vorzustellen, damit er weiß, wie man etwas analysiert. Sie sind nicht da, um Argumente zu validieren. Sie mögen in Bezug auf die Erwartungen der Menschen gebrochen sein, aber so viele Dinge sind es. :)
Brian D Foy
1

Die ausgezeichnete Bibliothek Type :: Tiny bietet ein Framework, mit dem Sie die Typprüfung in Ihren Perl-Code integrieren können. Was ich hier zeige, ist nur die dünnste Spitze des Eisbergs und verwendet Type :: Tiny auf einfachste und manuellste Weise.

Weitere Informationen finden Sie im Type :: Tiny :: Manual .

use Types::Common::String qw< NonEmptyStr >;

if ( NonEmptyStr->check($name) ) {
    # Do something here.
}

NonEmptyStr->($name);  # Throw an exception if validation fails
daotoad
quelle
-2

Wie wäre es mit

if (length ($name || '')) {
  # do something with $name
}

Dies entspricht nicht ganz Ihrer Originalversion, da es auch false zurückgibt, wenn $nameder numerische Wert 0 oder die Zeichenfolge ist '0', sich aber in allen anderen Fällen gleich verhält.

In Perl 5.10 (oder höher) wäre der geeignete Ansatz, stattdessen den definierten Operator oder Operator zu verwenden:

use feature ':5.10';
if (length ($name // '')) {
  # do something with $name
}

Dies entscheidet darüber, wie lang es sein soll, basierend darauf, ob $namees definiert ist und nicht, ob es wahr ist. 0 / '0'behandelt diese Fälle also korrekt, erfordert jedoch eine neuere Version von Perl, als viele Leute zur Verfügung haben.

Dave Sherohman
quelle
2
Warum mit einer kaputten Lösung anfangen, nur um zu sagen, dass sie kaputt ist?
Brian D Foy
Denn wie ich bereits erwähnte, ist 5.10 "eine neuere Version von Perl, als viele Leute zur Verfügung haben". YMMV, aber "dies ist eine 99% ige Lösung, von der ich weiß, dass Sie sie verwenden können, aber es gibt eine bessere, die Sie vielleicht verwenden können, vielleicht können Sie nicht" scheint mir besser zu sein als "hier ist die perfekte Lösung, aber Sie können wahrscheinlich" Verwenden Sie es nicht, also hier ist eine Alternative, mit der Sie wahrscheinlich als Fallback auskommen können. "
Dave Sherohman
1
Selbst mit früheren Perls können Sie eine funktionierende Lösung anstelle einer kaputten haben.
Brian D Foy
-3
if ($ name)
{
    #since undef und '' werden beide als false ausgewertet 
    #dies sollte nur funktionieren, wenn die Zeichenfolge definiert und nicht leer ist ...
    #wenn Sie nicht etwas wie $ name = "0" erwarten, das falsch ist.
    #notice obwohl $ name = "00" nicht falsch ist
}}
Joseph
quelle
1
Leider ist dies falsch, wenn $ name = 0;