用另一个文本文件中的行替换一个文本文件中的行

用另一个文本文件中的行替换一个文本文件中的行

我有一个 HLS 播放列表文件,如下所示:

#EXTM3U
#EXT-X-VERSION:3
#EXT-X-TARGETDURATION:12
#EXT-X-MEDIA-SEQUENCE:0
#EXTINF:12.500000,
playlist0.ts
#EXTINF:8.333333,
playlist1.ts
#EXTINF:12.500000,
playlist2.ts
....

然后我有一个带有链接 ( ) 的文件,signurls.txt如下所示:

https://example.com/playlist0.ts?Sign=xyz&Exp=1639139375&AWSAccessKeyId=abc
https://example.com/playlist1.ts?Sign=yzx&Exp=1639139375&AWSAccessKeyId=bca
https://example.com/playlist10.ts?Sign=zyx&Exp=1639139375&AWSAccessKeyId=cab
....

我正在尝试将链接插入到.m3u8文件中,如下所示:

....
#EXTINF:12.500000,
https://example.com/playlist0.ts?Sign=xyz&Exp=1639139375&AWSAccessKeyId=abc
....

我想出了这段脚本:

for f in *.ts; do 
   sed -i '' -e "'s|$f|`grep -e $f signurls.txt`|'" playlist.m3u8;
done

编辑:.ts文件中列出的每个文件都playlist.m3u8存在于当前目录中。我使用实际文件进行循环for,以便可以毫无问题地更改播放列表文件。

原因是-i ''它需要同时在 macOS 和 Linux 上运行。

我尝试回显该sed字符串,可以看到字符串扩展正在按预期工作。

但是,当我运行脚本时,出现此错误(一行脚本):

sed: 1: "'s|playlist0.ts|https:/ ...": invalid command code '

答案1

我将忽略这样一个事实:您似乎调用了something.ts您的代码显然正在使用的文件。你在文中没有提到这些,所以我就假装不知道。

$ cat urls
https://example.com/playlist0.ts?Sign=xyz&Exp=1639139375&AWSAccessKeyId=abc
https://example.com/playlist1.ts?Sign=yzx&Exp=1639139375&AWSAccessKeyId=bca
https://example.com/playlist10.ts?Sign=zyx&Exp=1639139375&AWSAccessKeyId=cab
$ cat playlist.m3u8
#EXTM3U
#EXT-X-VERSION:3
#EXT-X-TARGETDURATION:12
#EXT-X-MEDIA-SEQUENCE:0
#EXTINF:12.500000,
playlist0.ts
#EXTINF:8.333333,
playlist1.ts
#EXTINF:12.500000,
playlist2.ts
$ awk -F'[/?]' 'NR==FNR { pl[$4]=$0; next } /^[^#]/ && ($0 in pl) { $0 = pl[$0] }; 1' urls playlist.m3u8
#EXTM3U
#EXT-X-VERSION:3
#EXT-X-TARGETDURATION:12
#EXT-X-MEDIA-SEQUENCE:0
#EXTINF:12.500000,
https://example.com/playlist0.ts?Sign=xyz&Exp=1639139375&AWSAccessKeyId=abc
#EXTINF:8.333333,
https://example.com/playlist1.ts?Sign=yzx&Exp=1639139375&AWSAccessKeyId=bca
#EXTINF:12.500000,
playlist2.ts

上面的命令首先从第一个文件 ( )awk读取 URL ,并将字符串作为键添加到名为 的关联数组中。完整的 URL 将作为数组的值添加。urls.tspl

.ts通过将每个 URL 视为斜杠或问号分隔的字符串,并从该字符串中挑选出第四个字段,可以找到这些字符串。

然后,代码从.m3u8文件中读取行,并且对于不以字符开头的每一行#,它测试该行是否是数组中的键pl。如果是,则当前行将替换为数组中相应的 URL。.m3u8然后打印文件的所有行(可能如刚才所述进行修改)。

在上面的示例中,您可以看到播放列表文件中的最后一个条目未被替换,因为该条目的 URL 不在文件中urls

答案2

不要使用 shellwhile或 for 来loop处理文本。看为什么使用 shell 循环处理文本被认为是不好的做法?出于原因。

相反,使用perlorawkpythonor 任何非 shell 语言进行文本处理。

这是一个 perl 单行代码,它使用 Getopt::Std 模块(一个核心库模块,包含在 perl 中),允许使用signurls指定文件-s,以便可以读入并处理它分别地来自播放列表文件 - 这很重要,因为我们不想要选项要修改的文件-i

$signurls = shift;这可以用(第一个参数) 或(最后一个参数)之类的东西来完成,$signurls = pop;但是a)这意味着signurls文件成为第一个(或最后一个)参数(这是不灵活的,但对于快速而肮脏的黑客来说不一定是坏事),b)为 $signurls 提供默认文件名会更复杂且不太可靠,c)使用 来做到这一点并不难Getopt::Std,并且这是一个了解如何使用的有用的库模块。

所有后续参数都被视为播放列表文件。因为它们是用 a 处理的while(<>),所以可以通过 perl 的选项就地修改它们-i

$ perl -MGetopt::Std -i.bak -lpe '
  BEGIN {
    # Parse any command line options.
    getopts("s:", \%opts);
    my $signurls = $opts{s} // "signurls.txt";

    # Read in signurls file and build hash containing patterns
    # and replacement strings.
    open($fh,"<",$signurls) || die "error opening \"$signurls\": $!\n";
    while(<$fh>) {
      chomp;
      # Extract the "filename" portion of the URL and use it as the hash's key.
      # the hash's value is the URL itself.
      m=^.*://.*?/([^/]*)[/?].*=;
      $urls{$1} = $_;
    };
    close($fh);
  };

  foreach my $f (keys %urls) {
    if ($_ eq $f) {
      $_ = $urls{$f};
      last;   # we already matched, so there's no need to
              # compare this line against the remaining keys.
    };
  };' -s signurls.txt playlist.m3u38

注 1:.bakafter-i使 perl 为每个原始输入(播放列表)文件名创建备份副本(带有 .bak 扩展名)。

注2:BEGIN { ... }代码块被执行一次在打开或处理任何文件之前。对于输入文件中的每一行数据,BEGIN 块之外的脚本其余部分都会执行一次。

运行后的示例输出:

$ cat playlist.m3u8 
#EXTM3U
#EXT-X-VERSION:3
#EXT-X-TARGETDURATION:12
#EXT-X-MEDIA-SEQUENCE:0
#EXTINF:12.500000,
https://example.com/playlist0.ts?Sign=xyz&Exp=1639139375&AWSAccessKeyId=abc
#EXTINF:8.333333,
https://example.com/playlist1.ts?Sign=yzx&Exp=1639139375&AWSAccessKeyId=bca
#EXTINF:12.500000,
playlist2.ts

可以已经写了这个,以便它有一个-P播放列表文件的选项。事实上,我一开始就是这样写的。但是通过编写它来使用 perl 的-p选项(这只不过是一个自动while(<>)循环 - 请参阅 参考资料man perlrun)来读取和处理播放列表,我可以使用 perl 的-i选项并让脚本就地编辑播放列表文件,而无需编写我自己的就地编辑代码。它还添加了对处理多个输入文件的支持,无需任何额外的代码。两个有用的功能,免费。

答案3

sed在循环中使用

$  while read line; do sed -i.bak "s#$(sed 's#.*/\([^?]*\).*#\1#' <<< $line)#$line#" playlist.m3u8; done < signurls.txt

$ cat playlist.m3u8
#EXTM3U
#EXT-X-VERSION:3
#EXT-X-TARGETDURATION:12
#EXT-X-MEDIA-SEQUENCE:0
#EXTINF:12.500000,
https://example.com/playlist0.ts?Sign=xyzplaylist0.tsExp=1639139375playlist0.tsAWSAccessKeyId=abc
#EXTINF:8.333333,
https://example.com/playlist1.ts?Sign=yzxplaylist1.tsExp=1639139375playlist1.tsAWSAccessKeyId=bca
#EXTINF:12.500000,
playlist2.ts
....

答案4

给出的错误信息实际上比我想象的要好,事实证明确实是字符'的问题。

删除模式字符串'中的字符sed解决了问题:

for f in *.ts; do                                                                                                                            
    sed -i "" "s|$f|$(grep -e $f signurls.txt)|g" playlist.m3u8 ;                                                                            
done

相关内容