Home > Perl, Shell, Unix, загогулины, Программирование, Техноблог > Восхитительные грабли.

Восхитительные грабли.

April 28th, 2010 Leave a comment Go to comments

Я недавно открыл для себя восхитительные грабли. Оказывается, что я ходил по ним годами, а наступил только вчера.

Допустим, нам нужно скачать какой-нибудь файлик из сети, посчитать его размер и сохранить в файл.

Код на perl будет примерно таким:

#!/usr/bin/perl -w
use strict;

if (system('wget -O- http://sveshnikov.ru/httpd.sh | wc -с > /tmp/size') != 0) {
        die "OMG! Can't count bytes in httpd.sh file!!!";
}

В дальнейшем этот скрипт используется примерно так: if ./get_value; then работаем с /tmp/size; else сообщаем куда нужно; fi

Так вот, у этого скрипта хромает надежность. Почему? И как это сделать правильно? Жду ваших вариантов, мой – завтра :)

UPD:

Проблема заключается в том, что при использовании пайпов в $? окажется результат последней выполненной команды:

$ blablabla | wc; echo $?
zsh: command not found: blablabla
      0       0       0
0

Я решил этот вопрос с помощью bash и его опции pipefail. Пример выше я бы переписал как-то так:

#!/usr/bin/perl -w
use strict;                                                                                                                                                                    

sub my_system {
      return system('bash', '-o', 'pipefail', '-c', @_);
}                                                                                                                                                                              

unless (my_system('wget -O- http://sveshnikov.ru/httpd.sh | wc > /tmp/size') == 0) {
      die "OMG! Can't count bytes in httpd.sh file!!!";
}

Теперь будут отлавливаться все ошибки.
В шелл-скриптах можно еще анализировать массив PIPESTATUS, в котором сохраняется exit code для всех команд ковейера, но мне показалось, что это неудобно.

  1. April 28th, 2010 at 23:57 | #1

    Потому что wget есть далеко не везде, и нужно использовать православный curl? кстати опция -O- мне ни о чем не говорит. )

  2. Alexey Sveshnikov
    April 29th, 2010 at 00:09 | #2

    ixpict, нет, вы просто про неработоспособность говорите :)
    Представьте, что это фрагмент скрипта, который в зависимости от курса доллара меняет цены на товары в интернет-магазине. Этот скрипт написали, протестировали и выложили на сервер. Он работает несколько месяцев нормально, а однажды раз – и делает все товары в магазине бесплатными. Вот это хромающая надежность :)

    (кстати, опция -O говорит wget’у, куда сохранять файл. -O- – это значит не сохранять, а просто вывести в STDOUT)

  3. April 29th, 2010 at 00:31 | #3

    Я бы добавил ключик -q к вызову wget.

  4. Roman
    April 29th, 2010 at 01:05 | #4

    Наверное вы о том, что проверяется код возврата wc а не wget’а?

  5. Alexey Sveshnikov
    April 29th, 2010 at 01:57 | #5

    Roman, ага. И, по-моему, есть тонны кода, в котором это не учитывается.

  6. Alexey Sveshnikov
    April 29th, 2010 at 01:58 | #6

    antage, а я бы в реальном приложении добавил флажок -nv, а результат писал в лог :)

  7. grubberr
    April 29th, 2010 at 19:31 | #7

    а если такой вариант ?

    #!/usr/bin/perl -w

    use strict;
    use IPC::Open2;

    open(FH,’>’,'/tmp/size’) or die “Can’t open /tmp/size: $!\n”;
    my $pid1 = open2(“>&FH”, \*PH, ‘wc’,'-c’);
    close(FH);
    my $pid2 = open2(“>&PH”, \*STDIN, ‘wget’,'-O-’,’http://sveshnikov.ru/httpd.sh‘);
    close(PH);

    waitpid($pid2,0);
    my $ret2 = $? >> 8;
    waitpid($pid1,0);
    my $ret1 = $? >> 8;

    print “wc status => $ret1\n”;
    print “wget status => $ret2\n”;

  8. grubberr
    April 29th, 2010 at 19:35 | #8

    кстати в этом варианте нет никакого shell-а вообще, чистый perl :)

  9. Alexey Sveshnikov
    April 29th, 2010 at 21:07 | #9

    grubber, это сурово :) Ты всегда так пишешь? Мне лень :)

    Кстати, есть IPC::Run, с ним это же можно сделать в два раза короче, но все равно многословно.

  10. Konstantin Khomoutov
    April 30th, 2010 at 19:44 | #10

    ~$ tclsh
    % set failed [catch {exec -- wget -O - http://does.not.exist/httpd.sh | wc -l >/tmp/size} err]
    1
    % if {$failed} { puts stderr “OMFG, FAIL:\n$err” } else { puts “it works” }
    OMFG, FAIL:
    –2010-04-30 19:40:38– http://does.not.exist/httpd.sh
    Resolving does.not.exist… failed: Name or service not known.
    wget: unable to resolve host address `does.not.exist’
    ~$

    “Ну и что же!” — скажут любители перла — “Зато в перле есть оператор `yada-yada’!” :-)

  11. Alexey Sveshnikov
    May 1st, 2010 at 22:25 | #11

    Раз пошла такая пьянка, то:

    #!/usr/bin/perl -w
    use strict;
    use IPC::Run qw/run/;
    
    my ($lines, $err);
    
    run ['wget','-O-','http://sveshnikov.ru/httpd.sh', '2>', \$err,
    	'|', ['wc', '-c'], '>', \$lines, '2>', \$err
    or die "Error: $err";
    
    print "Successful! Output is: $lines";
    

    К тому же, в перле есть оператор yada-yada :)

  12. grubberr
    May 4th, 2010 at 15:56 | #12

    насколько помню IPC::Run нет в стандартной поставке

  13. Alexey Sveshnikov
    May 4th, 2010 at 16:14 | #13

    Что совсем не мешает его использовать :)
    В ubuntu это libipc-run-perl, в других дистрибутивах уверен, что тоже есть.

  1. No trackbacks yet.