Как в Perl кратко проверить, определена ли переменная $ и содержит ли она строку ненулевой длины?

83

В настоящее время я использую следующий Perl, чтобы проверить, определена ли переменная и содержит ли она текст. Я должен definedсначала проверить, чтобы избежать предупреждения о неинициализированном значении:

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

Есть ли лучший (предположительно более лаконичный) способ написать это?

Джессика
источник

Ответы:

80

Вы часто видите проверку на определенность, поэтому вам не нужно иметь дело с предупреждением об использовании значения undef (а в Perl 5.10 он сообщает вам ошибочную переменную):

 Use of uninitialized value $name in ...

Итак, чтобы обойти это предупреждение, люди придумывают всевозможные коды, и этот код начинает выглядеть как важная часть решения, а не как жевательная резинка и клейкая лента. Иногда лучше показать, что вы делаете, явно отключив предупреждение, которого вы пытаетесь избежать:

 {
 no warnings 'uninitialized';

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

В других случаях используйте какое-то нулевое значение вместо данных. С помощью оператора defined-or Perl 5.10 вы можете указать lengthявную пустую строку (определенную и вернуть нулевую длину) вместо переменной, которая вызовет предупреждение:

 use 5.010;

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

В Perl 5.12 это немного проще, потому lengthчто значение undefined также возвращает undefined . Это может показаться немного глупым, но это нравится математику, которым я, возможно, хотел бы быть. Это не вызывает предупреждения, поэтому этот вопрос существует.

use 5.012;
use warnings;

my $name;

if( length $name ) { # no warning
    ...
    }
Брайан Д Фой
источник
4
Кроме того, в версии 5.12 и новее length undefвозвращается undef вместо предупреждения и возврата 0. В логическом контексте undef так же ложно, как и 0, поэтому, если вы ориентируетесь на версию 5.12 или новее, вы можете просто написатьif (length $name) { ... }
rjbs
24

Как указывает мобрул, вы можете использовать следующее для небольшой экономии:

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

Вы можете отказаться от определенной проверки и получить что-то еще более короткое, например:

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

Но в случае, когда $nameне определено, хотя логический поток будет работать так, как задумано, если вы используете warnings(а вы должны использовать), вы получите следующее предупреждение:

Использование неинициализированного значения в строке ne

Итак, если есть вероятность, что это $nameможет не быть определено, вам действительно нужно в первую очередь проверить определенность, чтобы избежать этого предупреждения. Как указывает Синан Юнюр, вы можете использовать Scalar :: MoreUtils, чтобы получить код, который делает именно это (проверяет определенность, а затем проверяет нулевую длину) из коробки, с помощью empty()метода:

use Scalar::MoreUtils qw(empty);
if(not empty($name)) {
    # do something with $name 
}
Адам Беллэр
источник
17

Во-первых, поскольку lengthвсегда возвращает неотрицательное число,

if ( length $name )

и

if ( length $name > 0 )

эквивалентны.

Если вы согласны с заменой неопределенного значения пустой строкой, вы можете использовать //=оператор Perl 5.10, который присваивает RHS LHS, если LHS не определен:

#!/usr/bin/perl

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

my $name;

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

Обратите внимание на отсутствие предупреждений о неинициализированной переменной как $name присваивается пустая строка, если она не определена.

Однако, если вы не хотите зависеть от установки 5.10, используйте функции, предоставляемые Scalar :: MoreUtils . Например, приведенное выше можно записать как:

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

Если вы не хотите тупить $name, используйте default.

Синан Унюр
источник
+1 за упоминание "// =" (откуда я знал, что это будет ответ Синан :)
DVK
4
Я бы не стал использовать // = в этом случае, поскольку он меняет данные как побочный эффект. Вместо этого используйте немного короче length( $name // '' ).
brian d foy
@brian d'foy Я думаю, это зависит от того, что делается в функции.
Sinan Ünür
+1 Операторы //и //=, возможно, являются наиболее полезными из существующих специализированных операторов.
Крис Лутц,
1
Как отметил @rjbs в моем ответе, с v5.12 и более поздними версиями lengthтеперь может возвращаться что-то, что не является числом (но не NaN;)
brian d foy
5

В тех случаях, когда мне все равно, undefравна ли переменная или равна '', я обычно резюмирую ее как:

$name = "" unless defined $name;
if($name ne '') {
  # do something with $name
}
Гаурав
источник
В Perl 5.10 это можно сократить до того $name //= "";, что написал Синан.
Крис Лутц,
И даже если у вас нет Perl 5.10, вы все равно можете писать$name ||= "";
RET
1
@RET: нельзя использовать || здесь оператор, так как он заменяет строку '0' на ''. Вы должны проверить, определено ли оно, не правда ли.
brian d foy,
Крис, RET: Да, я знаю. Я специально пытался предложить, чтобы, если Джессику не волновала разница между undefи "", она должна просто заменить одно на другое и использовать один тест. Это не сработает в общем случае, для которого другие опубликованные решения намного лучше, но в этом конкретном случае приводит к аккуратному коду. Стоит ли перефразировать свой ответ, чтобы было понятнее?
Gaurav
1

Ты мог бы сказать

 $name ne ""

вместо

 length $name > 0
толпа
источник
7
Это по-прежнему будет предупреждать вас. Причина, по которой люди сначала проверяют определенность, состоит в том, чтобы избежать предупреждения о «неинициализированном значении».
brian d foy
1

Не всегда возможно делать повторяющиеся вещи просто и элегантно.

Просто делайте то, что делаете всегда, когда у вас есть общий код, который реплицируется во многих проектах:

Выполните поиск по CPAN, возможно, у кого-то уже есть код для вас. Для этой проблемы я нашел Scalar :: MoreUtils .

Если вы не найдете на CPAN того, что вам нравится, создайте модуль и вставьте код в подпрограмму:

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

Затем в своем коде назовите это:

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

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

Или , если вы возражаете прототипы и не возражают против дополнительных скобок, пропустить прототип в модуле, и назвать его как: is_nonempty($name).

Daotoad
источник
2
Разве это не похоже на убийство мухи молотком?
Зоран Симич,
4
@Zoran Нет. Выделение кода таким образом лучше, чем наличие сложных условий, повторяющихся во многих разных местах. Это как убить слона булавочными уколами. @daotoad: Я думаю, вам следует сократить свой ответ, чтобы подчеркнуть использование Scalar::MoreUtils.
Sinan Ünür
@Zoran: Scalar :: MoreUtils - очень легкий модуль без зависимостей. Его семантика также хорошо известна. Если у вас нет аллергии на CPAN, нет особых причин избегать его использования.
Адам Беллер,
1
@ Крис Лутц, да, я не должен. Но прототипы полуразрушены - есть простые способы нарушить принудительное исполнение прототипов. Например, дрянные и / или устаревшие руководства продолжают поощрять использование &сигилы при вызове функций. Поэтому я предпочитаю не полагаться на прототипы, чтобы сделать всю работу. Полагаю, я мог бы добавить к сообщению об ошибке «и прекратить использование символа & для дополнительных вызовов, если вы действительно это не имеете в виду».
daotoad
1
Про прототипы проще думать как о подсказках для компилятора perl, чтобы он знал, как что-то разбирать. Их нет для подтверждения аргументов. Они могут и не оправдать ожиданий людей, но так много всего. :)
brian d foy
1

Отличная библиотека Type :: Tiny предоставляет основу для встраивания проверки типов в ваш код Perl. То, что я здесь показываю, является лишь тончайшей вершиной айсберга и использует Type :: Tiny самым упрощенным и ручным способом.

Обязательно ознакомьтесь с 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
источник
-2

Как насчет

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

Это не совсем эквивалентно вашей исходной версии, так как она также вернет false, если $nameэто числовое значение 0 или строка '0', но будет вести себя так же во всех других случаях.

В Perl 5.10 (или новее) подходящим подходом было бы использование вместо этого оператора defined-or:

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

Это решит, что получить длину, в зависимости от того $name, определено ли это, а не от того, истинно ли это, поэтому 0 / '0'будет обрабатывать эти случаи правильно, но для этого требуется более свежая версия perl, чем есть у многих людей.

Дэйв Шерохман
источник
2
Зачем начинать с неработающего раствора только для того, чтобы сказать, что оно сломано?
brian d foy
Потому что, как я уже упоминал, 5.10 - это «более новая версия perl, чем доступно многим». YMMV, но «это решение на 99%, которое, я знаю, вы можете использовать, но есть лучшее решение, которое, возможно, вы можете использовать, возможно, вы не можете» кажется мне лучше, чем «вот идеальное решение, но вы, вероятно, можете» Я не использую его, так что вот альтернатива, которую вы, вероятно, можете использовать как запасной вариант ".
Дэйв Шерохман
1
Даже с более ранними версиями perl вы можете получить рабочее решение вместо сломанного.
brian d foy
-3
если ($ имя)
{
    #since undef и '' оба оцениваются как false 
    # это должно работать, только если строка определена и не пуста ...
    # если вы не ожидаете чего-то вроде $ name = "0", что неверно.
    # обратите внимание, что $ name = "00" не является ложным
}
Джозеф
источник
1
К сожалению, это будет неверно, если $ name = 0;