php – 为什么管道输出docker-compose exec到grep,打破它?

我正在运行此命令来运行Drush,它基本上是运行容器中Drupal的
PHP CLI:

docker-compose -f ../docker-compose.test.yml exec php scripts/bin/vendor/drush.phar -r public_html status-report

如果此命令正常,则为输出,它是有关容器中特定Drupal实例的状态信息的列表.我不会在这里粘贴它,因为它很长,而且无关紧要.

现在让我们通过将它传递给grep来过滤这些信息:

docker-compose -f ../docker-compose.test.yml exec php scripts/bin/vendor/drush.phar -r public_html status-report | grep -e Warning -e Error

结果是:

Cro  Error     L 
Gra  Warning   P 
HTT  Error     F 
HTT  Warning   T 
Dru  Warning   N 
XML  Error     L

哪个错了,看起来它已被切成碎片,而且大部分都丢失了.

现在,如果我们将通过添加-T标志来禁用伪tty的分配:

docker-compose -f ../docker-compose.test.yml exec -T php scripts/bin/vendor/drush.phar -r public_html status-report | grep -e Warning -e Error

输出是正确的:

Cron maintenance      Error     Last run 3 weeks 1 day ago                     
Gravatar              Warning   Potential issues                               
HTTP request status   Error     Fails                                          
HTTPRL - Non          Warning   This server does not handle hanging            
Drupal core update    Warning   No update data available                       
XML sitemap           Error     Last attempted generation on Tue, 04/18/2017 

这是为什么?

奖金问题,可能会通过前一个答案回答:使用-T是否有任何重要的副作用?

Docker version 18.06.1-ce, build e68fc7a215
docker-compose version 1.22.0

更新#1:

为简化起见,我将整个scripts / bin / vendor / drush.phar -r public_html status-report的正确输出保存到文件test.txt中并尝试:

 docker-compose -f ../docker-compose.test.yml exec php cat test.txt | grep -e Warning -e Error

有趣的是,输出现在是正确的,并且没有-T,所以它必须与Drush / php有关,尽管我仍然感兴趣可能是什么原因造成这种情况.

PHP 7.1.12 (cli) (built: Dec  1 2017 04:07:00) ( NTS )
Copyright (c) 1997-2017 The PHP Group
Zend Engine v3.1.0, Copyright (c) 1998-2017 Zend Technologies
    with Zend OPcache v7.1.12, Copyright (c) 1999-2017, by Zend Technologies
    with Xdebug v2.5.5, Copyright (c) 2002-2017, by Derick Rethans

Drush 8.1.17

更新#2:

为了进一步隔离问题,我将所有内容放在PHP文件中,即只打印它,然后:

docker-compose -f ../docker-compose.test.yml exec php php php.php | grep -e Warning -e Error

我得到了正确的输出!

所以它必须与Drush如何打印其消息有关,但我不知道它可能是什么.如果我们能够解决这个问题,这可能会非常有趣.

更新#3:

好的,这才是真正的魔力.问题也发生在没有任何命令的情况下运行drush,列出所有可用的命令.在输出传输时,命令列表会被破坏,因此可以在没有实际Drupal实例的情况下对其进行测试.

现在我想向你展示魔法.

在drush中,输出命令/ core / help.drush.phpin函数drush_core_help()中生成的可用命令列表.有这个电话:drush_help_listing_print($command_categories);我调查了一下.里面是一个调用drush_print_table($rows,FALSE,array(‘name’=> 20));它负责产生一部分被破坏的输出.

所以在其中,我决定在最后一次调用drush_print()之前拦截输出,添加简单的file_put_contents(‘/ var / www / html / data.txt’,$output);

而现在是时候对我来说绝对神奇了.

当我执行:

docker-compose -f ../docker-compose.test.yml exec php scripts/bin/vendor/drush/drush -r public_html

可以在此文件中检查最后一组命令,在我的例子中,它是:

 adminrole-update      Update the administrator role permissions.                                                                                                  
 elysia-cron           Run all cron tasks in all active modules for specified site using elysia cron system. This replaces the standard "core-cron" drush handler. 
 generate-redirects    Create redirects.                                                                                                                           
 libraries-download    Download library files of registered libraries.                                                                                             
 (ldl, lib-download)                                                                                                                                               
 libraries-list (lls,  Show a list of registered libraries.                                                                                                        
 lib-list)   

但是,如果我执行相同的命令,但输出将被管道或重定向,例如:

docker-compose -f ../docker-compose.test.yml exec php scripts/bin/vendor/drush/drush -r public_html | cat

不同的东西将被保存到一个文件中:

 adminrole-update      U 
                       p 
                       d 
                       a 
                       t 
                       e 
                       t 
                       h 
                       e 
                       a 
                       d 
                       m 
                       i 
                       n 
                       i 
                       s 
                       t 
                       r 
                       a 
                       t 
                       o 
                       r 
                       r 
(and the rest of the broken output)

因此,在管道/重定向实际发生之前,管道/重定向输出的事实会影响命令的执行.

这怎么可能呢? O_O

最佳答案 命令行程序根据其输出是否为终端来更改其输出表示并不罕见.例如,ls本身没有选项,以列式格式显示文件.管道传输时,输出将更改为每行一个文件的列表.你可以在
source code for GNU ls中看到这个:

case LS_LS:
  /* This is for the 'ls' program.  */
  if (isatty (STDOUT_FILENO))
    {
      format = many_per_line;
      set_quoting_style (NULL, shell_escape_quoting_style);
      /* See description of qmark_funny_chars, above.  */
      qmark_funny_chars = true;
    }
  else
    {
      format = one_per_line;
      qmark_funny_chars = false;
    }
  break;

您可以模拟ls |的行为…使用显式参数ls -1,这也并不罕见:隐式更改其输出表示的程序通常提供了一种明确使用该备用表示的方法.

对此的支持不仅仅是一个惯例:它实际上是ls in POSIX的要求:

The default format shall be to list one entry per line to standard output; the exceptions are to terminals or when one of the -C, -m, or -x options is specified. If the output is to a terminal, the format is implementation-defined.

这一切看起来都很神奇:ls怎么知道它跟随它有管道,因为它来到管道之前?答案非常简单,实际上:shell解析整个命令行,设置管道,然后将输入/输出分别连接到管道的相应程序分叉.

那么,该命令的哪一部分正在进行备用演示?我怀疑这是你的执行官的环境与column width calculation在匆忙中的相互作用.在我当地的环境中,匆匆帮助| ……不会产生任何异常结果.您可以尝试使用(或通过)cat -vet来发现输出中的任何异常字符.

也就是说,关于docker-compose:基于this thread,你不是唯一一个遇到过这个或类似问题的人.我没有搜索docker源代码,但是 – 通常 – 不分配伪tty将使另一端像非交互式shell一样,这意味着你的.bash_profile之类的东西不会运行而你不会能够在run命令中读取stdin.这可能会使外观无法正常工作.

上面链接的线程提到了这种形式的解决方法:

docker exec -i $(docker-compose ...) < input-file

考虑到-i的含义,这似乎是合理的,但对于基本脚本来说,它似乎也很复杂.

-T使它适用于你的事实向我建议你在.bash_profile(或类似的特定于登录shell的启动文件)中有一些东西正在改变某些值(可能是COLUMNS)或以这样的方式改变值.观察到有害影响.您可以尝试从这些文件中删除所有内容,然后重新添加它们以查看是否有任何特定问题导致问题.

点赞