【问题标题】:Command substitution in sedsed 中的命令替换
【发布时间】:2017-05-06 06:34:02
【问题描述】:

我想将文本文件的前六个字符读入一个字符串,并在该文件中的每个其他非空行前面加上该字符串。此类文件的示例可能是:

04/17 Walmart .toys $ 70 .cash $ -70

Caltex .gas 20 $ .cheque $ -20

McDonalds .burger 1 $ .cash $ -1

每个条目,即:每个非空行,都需要一个日期,为了便于数据输入,只在第一行输入了日期。条目由 1 个或多个空行分隔。输出如下所示:

04/17 Walmart .toys $ 70 .cash $ -70

04/17 Caltex .gas 20 $ .cheque $ -20

04/17 McDonalds .burger 1 $ .cash $ -1

我可以将非空字符串与^[^@]+[ ]*.[ ]([^;{}:]+)[ ]*$ 之类的内容匹配,但我不知道如何为非空行实际实现。

This Bash script 看起来对我很有吸引力,但我不知道如何在开头插入我的字符串。

我也无法在 StackOverflow 上找到我的问题的直接答案。

我尝试了一个接受文件名的脚本:

read -n 6 date < $1
sed 's/^/$(echo $date)/' | \
sed 's/^$(echo $date)\n//' | > $newName

我能够想出在每一行前面加上空格的日期(例如字符串:'04/17'),然后从后面没有任何内容的每一行中删除相同的日期。

但是,sed 似乎不接受命令替换:

sed: -e expression #1, char 10: unknown option to `s'

【问题讨论】:

    标签: bash sed


    【解决方案1】:

    你应该可以通过一个sed 命令来做到这一点:

    read -rn 6 date < "$1"
    sed -E 's#^([a-zA-Z]+)#'"$date"' \1#g' "$1" > newfile
    

    捕获组确保在插入日期之前行中至少有一个字符。

    编辑:根据您对问题的修改:

    newfile="output.txt"
    lineone=$(head -1 "$1");
    
    read -rn 6 date <<< "$lineone"
    sed -E 's#^([a-zA-Z]+)#'"$date"' \1#g; 1s#^.*$#'"$lineone"'#' "$1" > "$newfile" 
    

    由于您没有进行就地编辑,您可以进行 $date 插入,然后返回并换出第一行,因为它会以两个日期结束。可能有“更好”的方法来做到这一点,例如使用Perl,或丢失第二个sed 命令,尽管这至少应该让您了解它的工作原理......

    结果(新文件):

    04/17 Walmart .toys $ 70 .cash $ -70
    
    04/17 Caltex .gas 20 $ .cheque $ -20
    
    04/17 McDonalds .burger 1 $ .cash $ -1
    

    注意:在 sed 的某些版本中,扩展正则表达式的选项可以 可以是-r-E

    【讨论】:

    • 这很奇怪,它在这里似乎没有做任何事情,除了挂在我的 GNU sed 4.2.1 中。但我想我或多或少看到了你想要做的事情。
    • GNU sed 4.2.1 的联机帮助页没有提到“-E”选项。我尝试了“-r”选项,但仍然挂起。
    • 哈,我用while read 循环尝试了你的解决方案,经过一些小的编辑它就可以工作了。你的这个最新的,也是可行的解决方案是我认为最优雅的,所以我会去的。 -r 确实可以代替 -E,有趣的是 GNU sed 4.2.1 并没有反对使用 -E。感谢您的帮助!
    • 不客气!我猜你可能需要调整一些东西,但至少你现在应该有很多工作要做——干杯! :)
    【解决方案2】:

    bash回答:

    unset n
    while read -r x ; do
        case "${#n}$x" in 6) ;; 6*) x="$n$x" ;; *) n="${x:0:6}" ;; esac
        echo "$x"
    done < file > newfile
    

    输出:

    04/17 Walmart .toys $ 70 .cash $ -70
    
    04/17 Caltex .gas 20 $ .cheque $ -20
    
    04/17 McDonalds .burger 1 $ .cash $ -1
    

    【讨论】:

    • 我没有测试它,因为我已经使用了 I'L'I 的解决方案,但它看起来确实不错。
    【解决方案3】:

    使用 Perl:

    perl -plE 'if($.==1){$d=substr($_,0,6);next}elsif(/./){s/^/$d/}' file > new
    

    输出

    04/17 Walmart .toys $ 70 .cash $ -70
    
    04/17 Caltex .gas 20 $ .cheque $ -20
    
    04/17 McDonalds .burger 1 $ .cash $ -1
    

    或者在同一个文件中备份到file.bak

    perl -i.bak -plE 'if($.==1){$d=substr($_,0,6);next}elsif(/./){s/^/$d/}' file
    

    或相同的文件没有备份

    perl -i -plE 'if($.==1){$d=substr($_,0,6);next}elsif(/./){s/^/$d/}' file
    

    或者,如果您不确定日期中的前导零,

    perl -plE 'if($.==1){($d)=m|^(\d+/\d+\s)|;next}elsif(/./){s/^/$d/}' file
    

    将匹配第一行开头的任何digit(s) / digit(s) space

    正如评论中提到的l'L'l,上面也将日期添加到伪空行(该行看起来像空),例如它至少包含一个空格。在这种情况下,而不是/./:

    • 使用/\w/ - 因此,仅在包含至少一个单词字符的行前添加日期;
    • 或使用/\S/ - 当包含至少一个非空格字符时

    解释:

    perl -plE '                    # Run the commands on every input line and print them.
        if( $. == 1) {             # If it is the 1st line
            $d = substr($_, 0, 6); # take the first 6 characters and store it to $d
            next                   # And continue to the next line.
        }
        elsif( /\S/ ) {            # Else if the line contains any nonspace character
            s/^/$d/                # add to the beginning the content of $d
        }
        ' file > new
    

    【讨论】:

    • 非常好的答案,你得到了我的投票 :) — 虽然我可能会提到,但只有一个问题。如果空行以空格开头,那么您将以日期结束。
    • @l'L'l - 是的,使用/\w/ 可能比使用/./ 更好。谢谢。 :)
    • 是的,我也必须改变我的,无论如何都很好。
    • 看起来确实不错,只是...我不会“说”perl,这就是我选择 I'L'I's 答案的原因。很抱歉没有提到这一点。
    • @HarryH 当然没关系。 :) 对于有类似问题的其他用户,答案仍然存在。 :)
    【解决方案4】:

    斜杠终止sed命令,将分隔符更改为其他内容:

    "s#^#$(echo $date)#"
    

    你也可以这样写:

    "s#^#$date#"
    

    但是请注意,这种方法通常很脆弱(正如您所发现的那样),因为您不能将变量视为文字字符串。


    根据更新问题的示例,我建议使用单个 awk 命令来进行文本处理。像这样的东西可以给你示例输出:

    $ cat file
    04/17 Walmart .toys $ 70 .cash $ -70
    
    Caltex .gas 20 $ .cheque $ -20
    
    McDonalds .burger 1 $ .cash $ -1
    
    $ awk 'NR==1{d=$1}NR>1&&NF>0{$0=d" "$0}1' file
    04/17 Walmart .toys $ 70 .cash $ -70
    
    04/17 Caltex .gas 20 $ .cheque $ -20
    
    04/17 McDonalds .burger 1 $ .cash $ -1
    

    【讨论】:

    • 你在这两个方面都是对的。非常感谢!但是,read -n 6 date &lt; $1 省略了第 6 个字符,即:空格,所以我不得不手动添加它。不过没问题。然后是"s#^$date#\n#" 没有做我想要做的事情的问题:用空行替换只有日期(和空格)的行,这是可以理解的。知道如何获得这种效果吗?
    • @HarryH:我不确定我是否理解要求...您能否更新问题以包含示例输入数据和该数据的预期输出?
    • 我刚刚添加了输入和预期输出样本。
    • @HarryH:我看到你已经接受了一个答案,但我添加了另一个替代方案 awk,在我看来,出于效率和代码清晰度的原因,这更可取。例如,如果文件第一行的第一个字段包含哈希字符#,则接受的答案将失败。
    • 这是一个非常好的短代码!抱歉,我已经选择了答案,这对我也有用,并且使用了我有点理解的 sed,这与 awk 相反。但是你的解决方案真的很好看。
    猜你喜欢
    • 1970-01-01
    • 2020-09-22
    • 1970-01-01
    • 2022-01-10
    • 2019-02-11
    • 2018-09-16
    • 2016-01-19
    • 2023-01-02
    • 2017-12-05
    相关资源
    最近更新 更多