【问题标题】:How to load a multiline setting from a file using bash?如何使用 bash 从文件加载多行设置?
【发布时间】:2022-11-16 04:54:40
【问题描述】:

我有一个很长的配置文件,看起来像:

<some stuff before our example>
    'Realtime' => [
        'foo' => 'bar',
        'enabled' => true,
        'lorem' => 'ipsum'
    ],
<some stuff after our example>

上面是一个大型配置 php 文件,我被要求使用 bash 挖掘“实时”的 enabled 值。我可以用 PHP 来做,但我被特别要求用 bash 来做。

我尝试了以下内容:

echo $(tr '\n' ' ' < myconfig.php | sed '$s/ $/\n/') | grep -o -P '(?<=Realtime).*(?=\])'

这会从 Realtime 之间的文件中挖掘文本最后一个]。但是我想挖掘Realtime和第一个]之间的内容。目前我已经实现了一个简单的 bash 并伴随着 PHP 解析器,如下所示:

    public function getConfig($name)
    {
        $path = Paths::CONFIG_FILE;
        if (!$this->config) {
            $this->config = Command_ShellFactory::makeForServer('zl', "cat {$path}")->execute(true, true);
        }
        $splitName = explode('.', $name);
        $lastPosition = 0;
        $tempConfig = $this->config;
        foreach ($splitName as $currentName) {
            if (($position = strpos($tempConfig, $currentName)) === false) {
                throw new RuntimeException('Setting was not found');
            }

            $tempConfig = substr($tempConfig, $position);
        }

        return trim(explode("=>", explode("\n", $tempConfig)[0])[1], ", \n\r\t\v\x00");
    }

这可行,但我对它不满意,因为它通过 shell 命令将整个文件加载到内存中,然后搜索嵌套键(Realtime.enabled 传递给它)。是否有可能以所有逻辑都通过 bash 发生而不是通过 PHP 帮助它的方式改进此代码?

编辑

要挖掘的可能设置可以是任何深度。例子:

[
    /*...*/
    'a' => 'b', //Depth of 1
    'c' => [
        'a' => 'd' //Depth of 2
    ],
    'e' => [
        'f' => [
            'g' =>'h' //Depth of 3
        ]
    ]
    /*...*/
]

理论上任何数量的深度都是可能的,在示例中我们有深度 1、深度 2 和深度 3。

编辑

我创建了 foo.sh(一些不重要的幻想名称):

[
    'Realtime' => [
        'enabled' => [
            'd' => [
                'e' => 'f'
            ]
        ],
        'a' => [
            'b' => 'c'
        ]
    ]
    'g' => [
        'h' => 'i'
    ]
    'Unrealtime' => 'abc'
]

工作一维命令:

sed -Ez ":a;s/.*Unrealtime' => +([^,]*).*/\1\n/" foo.sh | head -1

结果是

'abc'

工作二维命令:

sed -Ez ":a;s/.*g[^]]*h' => +([^,]*).*/\1\n/" foo.sh | head -1

结果是

'一世'

三维命令:

sed -Ez ":a;s/.*Realtime*[^]]*a[^]]*b' => +([^,]*).*/\1\n/" foo.sh | head -1

当且仅当

    'a' => [
        'b' => 'c'
    ]

Realtime 的第一个孩子。所以,缺少了一些东西,因为我需要避免假设我搜索的元素是第一个孩子。

工作四维命令:

sed -Ez ":a;s/.*Realtime[^]]*enabled[^]]*d[^]]*e' => +([^,]*).*/\1\n/" foo.sh | head -1

同样,它仅在 enabledRealtime 的第一个孩子时才有效。我正在修改上面的测试用例,更改 Realtime 的子项的顺序。所以,似乎这个表达式中唯一缺少的东西是指定我们不一定要寻找第一个孩子的东西。

【问题讨论】:

  • 用 bash 解析 PHP 数据似乎适得其反。为什么给你这个要求?
  • @glennjackman 这是违反直觉的,我不知道为什么客户想要这样。我有一个可行的解决方案,但我认为它不是很优雅。
  • 查看最新更新...如果有人要求e -&gt; f,预期结果是什么?你返回子部分/数组 - [ 'g' =&gt; 'h' ] 吗?你什么都不退吗?还有什么?
  • 旁白:你为什么使用cat而不是file_get_contents($path)
  • @markp-fuso e -> f 是不合理的期望。在这种情况下,将要求 e -> f -> g。

标签: php bash sed grep cat


【解决方案1】:

一个awk的想法:

awk -F"'" '                                             # define input field delimiter as single quote
$2 == "Realtime"           { inblock=1 }                # if 2nd field == "Realtime" then set flag
$2 == "enabled" && inblock { if (NF==3) {               # value is not wrapped in single quotes
                                pos=index($0,"=>")      # find location of "=>" string
                                value=substr($0,pos+2)  # grab everything after "=>"
                                gsub(/[ ,]/,"",value)   # remove all spaces and commas
                             }
                             else                       # value is wrapped in single quotes
                                value=$4                # grab 4th field
                             print value
                             exit                       # no need to process rest of file so exit script
                           }
' myconfig.php

这会产生:

true

笔记:此解决方案基于提供的示例进行硬编码

【讨论】:

  • 这工作正常,但我不明白,不幸的是,bash 不是我的主要优势。你能解释一下这个解决方案吗?
  • @LajosArpad 添加了一些 cmet;从技术上讲,这不是bashawk 是一个二进制文件,通常在很多 shell 下可用...bashshdash
  • 问题是如果我不理解这个解决方案,我将无法应用它,因为我可能有不同的深度。它可以是 'foo' => 'bar' 形式的简单设置,但它可以是设置内的设置内的设置......现在我们需要这个用于我描述的单个示例,但我'我正在尝试实施更通用的解决方案。
  • @LajosArpad 对于通用解决方案,您必须编写一个 PHP 解析器...
  • 更新了解决值可能或可能不包含在单引号中的问题的答案(例如,true vs 'bar ipsum'); (仍然)没有解决搜索的可变深度
【解决方案2】:

假设字符串Realtime在文件中只出现一次,你可以试试这个sed

$ sed -Ez "s/.*Realtime[^]]*enabled' => +([^,]*).*/
/" myconfig.php
true

【讨论】:

  • 这太棒了!我怎么能用任意长度做到这一点?如果我必须在 Setting 数组中的名为 Realtime 的数组中找到 enabled 的值,这将如何改变?
  • 另外,如果 Realtime 是单个值(不是数组),将如何修改?
  • @LajosArpad 你能提供相关阵列的样本吗?
  • 得到它的工作。如果我执行 sed -Ez ":a;s/.*Realtime[^]]*enabled[^]]*d[^]]*e' => +([^,]*).*/ /" foo.sh | head -1,它会起作用,假设 [] 字符与内容不在同一行
  • @LajosArpad 你的代码通过管道传输到head 似乎仍然留下尾括号,至少对我来说是这样。我认为我在 cmets 中提供的答案更合适,但是,我没有你的真实数据可以使用,所以如果解决方案适合你,那很好,我很乐意提供帮助。但是,由于它不适用于您提供的示例,我想我会保留有效的代码。如果未来的读者有类似的问题,可以阅读这些 cmets。
【解决方案3】:

根据您对trgrep 的测试:

#! /usr/bin/env bash

tr -d "
" < "myconfig.php" 
| grep -o "'Realtime' => [[^]*]" 
| grep -oE "'enabled' => (true|false)" 
| head -1 
| cut -d " " -f 3

笔记:

  • 如果你的文件中只有一个Realtime块,| head -1如果没有必要
  • 如果您不确定空格的数量,请删除所有空格并更改下一个过滤器,如下所示:
#! /usr/bin/env bash

tr -d "
" < "myconfig.php" 
| tr -d " " 
| grep -o "'Realtime'=>[[^]*]" 
| grep -oE "'enabled'=>(true|false)" 
| head -1 
| cut -d ">" -f 2

更新

更通用的解决方案:

#! /usr/bin/env bash

INPUT_FILE="$1"
LEVEL_1_OBJ="Settings"
LEVEL_N_OBJ="Realtime"
FIELD="enabled"

tr -d "
" < "${INPUT_FILE}" 
| tr -d " " 
| grep -o "'${LEVEL_1_OBJ}'=>[.*" 
| grep -o "'${LEVEL_N_OBJ}'=>[[^]*]" 
| grep -oE "'${FIELD}'=>[^,]*" 
| head -1 
| cut -d ">" -f 2

您云在 1 和 N(最后一个)之间添加级别过滤器和/或每个 grep 命令之间的任何过滤器

【讨论】:

  • 出色的!如果这是一个嵌套数组,那么我将搜索 Settings 数组,在其中搜索 Realtime 数组,在其中搜索 enabled,这将如何改变?
  • 首先将 grep 更改为 | grep -o "'Settings'=&gt;[.*'Realtime'=&gt;[[^]*]" (在第二个代码块中)
  • 我懂了!让我试试。谢谢!
  • 如果不起作用,请用示例更新您的请求
  • 我已经相应地编辑了我的问题。
【解决方案4】:

无法可靠地回答您的问题。

恕我直言,不太糟糕的解决方案是包含您的配置文件并输出请求的设置。

这是一个配置文件的示例,它显示了在不解释 PHP 的情况下从中挖掘设置是多么可怕:

<?php
$val = 'h';
$config = [
    'a' => 'b',
    'c' => array ( 'a' => 'd', 'b' => " /* "
    ), 'e' => [
        'f' => [ 'g' => $val ]
/*
        'f' => [
            'g' =>'h' */
]];

现在,如果您从运行 PHP 的 shell 脚本中包含它:

#!/usr/bin/env php
<?php
    require("/path/to/configfile.php");

    $path = explode(".",$argv[1]);
    $result = $config;

    while ($path) {
        if (!is_array($result))
            exit(1);
        $p = array_shift($path);
        if (!array_key_exists($p,$result))
            exit(1);
        $result = $result[$p];
    }

    var_dump($result);

然后你就可以准确地得到一个设置值,只要配置文件不依赖于环境设置(与命令行相比,web 服务器中的环境设置会有所不同):

#!/bin/sh
./script.php a
./script.php c.a
./script.php e.f.g
string(1) "b"
string(1) "d"
string(1) "h"

【讨论】:

  • 那是我想做的,但门票中明确禁止这样做。
  • @LajosArpad 您知道不使用 PHP 是不可能完成的任务吗?检查我的示例配置,你将如何处理 cmets 或语法差异?即使使用 PHP,也不能保证准确,因为服务器环境和 CLI 环境之间存在差异。
猜你喜欢
  • 1970-01-01
  • 2010-11-14
  • 1970-01-01
  • 1970-01-01
  • 2019-02-15
  • 1970-01-01
  • 2014-02-23
  • 1970-01-01
  • 2010-11-04
相关资源
最近更新 更多