Jak w Perlu mogę zwięźle sprawdzić, czy zmienna $ jest zdefiniowana i zawiera ciąg o niezerowej długości?

83

Obecnie używam następującego Perla, aby sprawdzić, czy zmienna jest zdefiniowana i zawiera tekst. Muszę definednajpierw sprawdzić, aby uniknąć ostrzeżenia o niezainicjowanej wartości:

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

Czy istnieje lepszy (prawdopodobnie bardziej zwięzły) sposób, aby to napisać?

Jessica
źródło

Odpowiedzi:

80

Często widzisz sprawdzanie zdefiniowania, więc nie musisz zajmować się ostrzeżeniem o używaniu wartości undef (aw Perlu 5.10 mówi ona o niewłaściwej zmiennej):

 Use of uninitialized value $name in ...

Tak więc, aby obejść to ostrzeżenie, ludzie wymyślają różnego rodzaju kod, który zaczyna wyglądać jak ważna część rozwiązania, a nie jak guma balonowa i taśma klejąca. Czasami lepiej jest pokazać, co robisz, wyraźnie wyłączając ostrzeżenie, którego próbujesz uniknąć:

 {
 no warnings 'uninitialized';

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

W innych przypadkach użyj wartości null zamiast danych. Za pomocą operatora zdefiniowanego lub w Perlu 5.10 możesz podać lengthjawny pusty ciąg (zdefiniowany i zwrócić zerową długość) zamiast zmiennej, która wyzwoli ostrzeżenie:

 use 5.010;

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

W Perlu 5.12 jest to trochę łatwiejsze, ponieważ lengthna niezdefiniowanej wartości również zwraca undefined . Może się to wydawać trochę głupie, ale to podoba się matematykowi, którym chciałbym być. To nie powoduje ostrzeżenia, co jest powodem, dla którego istnieje to pytanie.

use 5.012;
use warnings;

my $name;

if( length $name ) { # no warning
    ...
    }
brian d foy
źródło
4
Ponadto w wersji 5.12 i nowszych length undefzwraca undef, zamiast ostrzeżenia i zwracania 0. W kontekście logicznym undef jest tak samo fałszywe jak 0, więc jeśli celujesz w wersję 5.12 lub nowszą, możesz po prostu napisaćif (length $name) { ... }
rjbs
24

Jak wskazuje mobrule, zamiast niewielkich oszczędności można użyć następujących rozwiązań:

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

Możesz zrezygnować ze zdefiniowanego czeku i dostać coś jeszcze krótszego, np .:

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

Ale w przypadku, gdy $namenie jest zdefiniowane, chociaż przepływ logiczny będzie działał zgodnie z zamierzeniami, jeśli używasz warnings(a powinieneś), otrzymasz następujące ostrzeżenie:

Użycie niezainicjowanej wartości w ciągu ne

Tak więc, jeśli istnieje szansa, która $namemoże nie zostać zdefiniowana, naprawdę musisz przede wszystkim sprawdzić zdefiniowanie, aby uniknąć tego ostrzeżenia. Jak wskazuje Sinan Ünür, możesz użyć Scalar :: MoreUtils, aby uzyskać kod, który robi dokładnie to (sprawdza zdefiniowanie, a następnie sprawdza zerową długość) po wyjęciu z pudełka, za pomocą empty()metody:

use Scalar::MoreUtils qw(empty);
if(not empty($name)) {
    # do something with $name 
}
Adam Bellaire
źródło
17

Po pierwsze, ponieważ lengthzawsze zwraca liczbę nieujemną,

if ( length $name )

i

if ( length $name > 0 )

są równoważne.

Jeśli nie masz nic przeciwko zamianie niezdefiniowanej wartości na pusty ciąg, możesz użyć //=operatora Perl 5.10, który przypisuje RHS do LHS, chyba że LHS jest zdefiniowany:

#!/usr/bin/perl

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

my $name;

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

Zwróć uwagę na brak ostrzeżeń o niezainicjowanej zmiennej, ponieważ $nameprzypisywany jest pusty ciąg, jeśli jest niezdefiniowany.

Jeśli jednak nie chcesz polegać na instalacji 5.10, użyj funkcji dostarczonych przez Scalar :: MoreUtils . Na przykład powyższe można zapisać jako:

#!/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";

Jeśli nie chcesz bić $name, użyj default.

Sinan Ünür
źródło
+1 dla wzmianki „// =” (skąd wiedziałem, że to będzie odpowiedź Sinana :)
DVK,
4
Nie użyłbym // = w tym przypadku, ponieważ zmienia to dane jako efekt uboczny. Zamiast tego użyj nieco krótszego length( $name // '' ).
brian d foy
@brian d'foy Myślę, że to zależy od tego, co jest wykonywane w funkcji.
Sinan Ünür
+1 Operatory //i //=są prawdopodobnie najbardziej użytecznymi spośród istniejących operatorów wyspecjalizowanych.
Chris Lutz
1
Jak @rjbs wskazał w mojej odpowiedzi, w wersji 5.12 i nowszych lengthmożna teraz zwrócić coś, co nie jest liczbą (ale nie NaN;)
brian d foy
5

W przypadkach, w których nie obchodzi mnie, czy zmienna jest undefrówna lub równa '', zwykle podsumowuję to jako:

$name = "" unless defined $name;
if($name ne '') {
  # do something with $name
}
Gaurav
źródło
W Perlu 5.10 można to skrócić, do $name //= "";czego właśnie napisał Sinan.
Chris Lutz,
I nawet jeśli nie masz perla 5.10, nadal możesz pisać$name ||= "";
RET
1
@RET: nie możesz użyć || operator, ponieważ zastępuje ciąg „0” przez „”. Musisz sprawdzić, czy jest zdefiniowana, a nie prawda.
brian d foy
Chris, RET: Tak, wiem. W szczególności próbowałem zasugerować, że jeśli Jessica nie była zaniepokojona różnicą między undefa "", powinna po prostu zmienić jedno na drugie i użyć jednego testu. To nie zadziała w ogólnym przypadku, dla którego inne opublikowane rozwiązania są znacznie lepsze, ale w tym konkretnym przypadku prowadzi do zgrabnego kodu. Czy powinienem przeformułować moją odpowiedź, aby była jaśniejsza?
Gaurav
1

Można powiedzieć

 $name ne ""

zamiast

 length $name > 0
tłum
źródło
7
To nadal będzie ostrzeżenie. Powodem, dla którego ludzie najpierw sprawdzają zdefiniowaną definicję, jest uniknięcie ostrzeżenia o niezainicjowanej wartości.
brian d foy
1

Nie zawsze jest możliwe robienie powtarzalnych rzeczy w prosty i elegancki sposób.

Po prostu rób to, co zawsze robisz, gdy masz wspólny kod, który jest replikowany w wielu projektach:

Wyszukaj CPAN, ktoś może już mieć dla Ciebie kod. W tym numerze znalazłem Scalar :: MoreUtils .

Jeśli nie znajdziesz czegoś, co ci się podoba w CPAN, utwórz moduł i umieść kod w podprogramie:

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

Następnie w swoim kodzie nazwij to:

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

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

Lub jeśli sprzeciwiasz się prototypom i nie sprzeciwiasz się dodatkowym parom, pomiń prototyp w module i wywołaj go w ten sposób: is_nonempty($name) .

daotoad
źródło
2
Czy to nie jest jak użycie młotka do zabicia muchy?
Zoran Simic
4
@Zoran Nie. Taki kod faktoryzacji bije skomplikowany stan powielany w wielu różnych miejscach. To by było jak użycie punkcików do zabicia słonia. @daotoad: Myślę, że powinieneś skrócić swoją odpowiedź, aby podkreślić użycie Scalar::MoreUtils.
Sinan Ünür
@Zoran: Scalar :: MoreUtils to bardzo lekki moduł bez żadnych zależności. Jego semantyka jest również dobrze znana. Jeśli nie jesteś uczulony na CPAN, nie ma powodu, aby go unikać.
Adam Bellaire,
1
@Chris Lutz, tak, nie powinienem. Ale prototypy są częściowo zepsute - istnieją łatwe sposoby na złamanie ich egzekwowania. Na przykład kiepskie i / lub przestarzałe samouczki nadal zachęcają do używania &sigila podczas wywoływania funkcji. Dlatego nie polegam na prototypach, aby wykonać całą pracę. Przypuszczam, że mógłbym dodać „i zakończyć używanie & sigil w wywołaniach podrzędnych, chyba że naprawdę masz to na myśli” do komunikatu o błędzie.
daotoad
1
Łatwiej jest myśleć o prototypach jako wskazówkach dla kompilatora perla, aby wiedział, jak coś przeanalizować. Nie ma ich po to, by weryfikować argumenty. Mogą być złamane pod względem oczekiwań ludzi, ale tak wiele rzeczy jest. :)
brian d foy
1

Doskonała biblioteka Type :: Tiny zapewnia strukturę, za pomocą której można wbudować funkcję sprawdzania typów w kodzie Perla. To, co tutaj pokazuję, to tylko najcieńszy wierzchołek góry lodowej i używa Type :: Tiny w najbardziej uproszczony i ręczny sposób.

Aby uzyskać więcej informacji, zapoznaj się z 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
źródło
-2

Co powiesz na

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

Nie jest to w pełni równoważne z wersją oryginalną, ponieważ zwróci również fałsz, jeśli $namebędzie wartością liczbową 0 lub ciągiem '0', ale będzie zachowywać się tak samo we wszystkich innych przypadkach.

W perlu 5.10 (lub nowszym) właściwym podejściem byłoby użycie operatora zdefiniowanego lub zamiast:

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

To zadecyduje, jaką długość otrzymać w oparciu o $nameto, czy jest zdefiniowana, a nie czy to prawda, więc 0 / '0'będzie poprawnie obsługiwać te przypadki, ale wymaga nowszej wersji perla niż wiele osób jest dostępnych.

Dave Sherohman
źródło
2
Po co oszukiwać zepsutym rozwiązaniem tylko po to, by powiedzieć, że jest zepsute?
brian d foy
Ponieważ, jak również wspomniałem, 5.10 to „nowsza wersja perla, niż jest dostępna dla wielu osób”. YMMV, ale „wiem, że jest to rozwiązanie w 99%, z którego możesz skorzystać, ale jest lepsze rozwiązanie, którego możesz użyć, a może nie” wydaje mi się lepsze niż „oto idealne rozwiązanie, ale prawdopodobnie możesz” Nie używaj go, więc oto alternatywa, z której prawdopodobnie możesz sobie poradzić jako rozwiązanie awaryjne. "
Dave Sherohman
1
Nawet z wcześniejszymi perlami możesz mieć działające rozwiązanie zamiast zepsutego.
brian d foy
-3
if ($ name)
{
    #since undef i „” oba oceniają na false 
    # to powinno działać tylko wtedy, gdy łańcuch jest zdefiniowany i niepusty ...
    # chyba że spodziewasz się czegoś takiego jak $ name = "0", co jest fałszem.
    # zauważ, że $ name = "00" nie jest fałszem
}
Józef
źródło
1
Niestety będzie to fałsz, gdy $ name = 0;