Don't use a shell while
or for loop
to process text. See Why is using a shell loop to process text considered bad practice? for reasons why.
Instead, use perl
or awk
or python
or any-language-that-isn't-shell for text processing.
Here's a perl one-liner which uses the Getopt::Std module (a core library module, included with perl), allowing the signurls
file can be specified with -s
, so that it can be read in and processed separately from the playlist file(s) - this is important because we don't want that file to be modified by the -i
option.
This could have been done with something like $signurls = shift;
(first arg) or $signurls = pop;
(last arg) but a) that would mean the signurls file had to be the very first (or last) argument (which is inflexible, but not necessarily a bad thing for a quick-and-dirty hack), b) it would have been more complicated and less reliable to have a default filename for $signurls, and c) it's really no harder to do it with Getopt::Std
, and that's a useful library module to know how to use.
All subsequent arguments are treated as playlist file(s). Because they are processed with a while(<>)
, they can be modified in-place by perl's -i
option:
$ 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
Note 1: the .bak
after -i
causes perl to make a backup copy (with .bak extension) of each original input (playlist) filename.
Note 2: the BEGIN { ... }
code block is executed once before any files are opened or processed. The remainder of the script, outside of the BEGIN block, is executed once for each and every line of data from the input file(s).
Sample output after run:
$ 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
I could have written this so that it had a -P
option for the playlist file. In fact, I first started writing it that way. But by writing it to use perl's -p
option (which is not much more than an automatic while(<>)
loop - see man perlrun
) to read and process the playlists, I could use perl's -i
option and have the script edit the playlist file(s) in-place without having to write my own in-place editing code. It also added support for processing multiple input files without any additional code required. Two useful features, for free.