为什么将 docker-compose exec 的管道输出传输到 grep 会破坏它?

Łuk*_*oda 5 php grep drush docker docker-compose

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

docker-compose -f ../docker-compose.test.yml exec php scripts/bin/vendor/drush.phar -r public_html status-report
Run Code Online (Sandbox Code Playgroud)

如果此命令正常,输出是有关容器中特定 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
Run Code Online (Sandbox Code Playgroud)

结果是:

Cro  Error     L 
Gra  Warning   P 
HTT  Error     F 
HTT  Warning   T 
Dru  Warning   N 
XML  Error     L
Run Code Online (Sandbox Code Playgroud)

这是不对的,看起来像是被切成了碎片,而且大部分都不见了。

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

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
Run Code Online (Sandbox Code Playgroud)

输出是正确的:

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 
Run Code Online (Sandbox Code Playgroud)

这是为什么?

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

Docker version 18.06.1-ce, build e68fc7a215
docker-compose version 1.22.0
Run Code Online (Sandbox Code Playgroud)

更新#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
Run Code Online (Sandbox Code Playgroud)

有趣的是,现在的输出是正确的-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
Run Code Online (Sandbox Code Playgroud)

更新#2:

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

docker-compose -f ../docker-compose.test.yml exec php php php.php | grep -e Warning -e Error
Run Code Online (Sandbox Code Playgroud)

我得到了正确的输出!

所以它必须与 Drush 打印消息的方式有关,但我不明白它是什么。如果我们能弄清楚这一点,那可能会非常有趣。

更新#3:

好吧,伙计们,这确实是魔法。drush在没有任何命令的情况下运行以列出所有可用命令时也会出现此问题。当输出通过管道传输时,命令列表被破坏,因此可以在没有实际 Drupal 实例的情况下进行测试。

现在我想向你们展示魔法。

在 中,输出在函数drush中生成的可用命令列表。有这样的电话:我调查了它。里面有一个调用,负责生成部分被破坏的输出。commands/core/help.drush.phpdrush_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
Run Code Online (Sandbox Code Playgroud)

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

 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)   
Run Code Online (Sandbox Code Playgroud)

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

docker-compose -f ../docker-compose.test.yml exec php scripts/bin/vendor/drush/drush -r public_html | cat
Run Code Online (Sandbox Code Playgroud)

不同的内容将被保存到文件中:

 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)
Run Code Online (Sandbox Code Playgroud)

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

这怎么可能呢?奥奥

bis*_*hop 2

命令行程序根据其输出是否是终端来更改其输出表示形式并不罕见。例如,ls在没有任何选项的情况下,它本身以柱状格式显示文件。通过管道传输时,输出将更改为每行一个文件的列表。您可以在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;
Run Code Online (Sandbox Code Playgroud)

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

对此的支持不仅仅是一个约定:它实际上是ls POSIX的要求:

默认格式应为每行列出一个条目到标准输出;终端或指定 -C、-m 或 -x 选项之一时除外。如果输出到终端,则格式是实现定义的。

这一切看起来很神奇:既然它在管道之前,怎么知道它后面ls有管道呢?答案实际上非常简单:shell 解析整个命令行,设置管道,然后使用适当连接到管道的输入/输出来分叉相应的程序。


那么,命令的哪一部分正在执行交替演示呢?我怀疑这是你的环境exec和drush 中的列宽计算之间的相互作用。在我当地的环境中,drush help | ...不会产生任何异常结果。您可以尝试通过管道传输(或通过)cat -vet以发现输出中的任何异常字符。


也就是说,docker-compose具体而言:基于此线程,您并不是唯一遇到此问题或类似问题的人。我没有搜罗 docker 源代码,但是 - 一般来说 - 不分配伪 tty 会使另一端表现得像一个非交互式 shell,这意味着像你这样的东西.bash_profile将无法运行,并且你将无法在运行命令中读取标准输入。这可能会导致事情看起来不起作用。

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

docker exec -i $(docker-compose ...) < input-file
Run Code Online (Sandbox Code Playgroud)

考虑到 的含义,这似乎是合理的-i,但对于基本脚本编写来说,它似乎也相当复杂。

-T让它为你工作的事实告诉我,你的.bash_profile(或类似的特定于登录外壳的启动文件)中有一些东西正在改变某些值(也许COLUMNS)或以观察到的有害的方式改变这些值影响。您可以尝试删除这些文件中的所有内容,然后将它们添加回来,看看是否有任何特定文件导致了问题。