perl -wnlE'say s/^.+?:\s\K (.*[:-].*)/\x27$1\x27/rx' file.txt
The \K
drops all previous matches (from $&
) so they stay in the string and we don't have to capture them and put them back. The \x27
is for a single quote, used in the question.
The /r
modifier has the substitution return the changed string (or the original if the pattern didn't match), which is then printed; the original isn't changed. See modifiers in perlre. The output can be redirected into a file,
perl -wnlE'...' file.txt > out.txt
or the input file can be changed in-place with the -i
switch
perl -i.bak -wnlE'...' file.txt
The .bak
part makes it also save a backup with that extension. See switches in perlrun.
This assumes that the patterns of interest are always contained within one line.
Not sure whether one would call it "elegant" ...
As indicated in the question, and clarified in a comment, the input is a multiline string in a program, not a file. In order to process the whole string at once the regex above needs one change and different modifiers
use warnings;
use strict;
use feature 'say';
my $data = <<'EOF';
data:
normal: text
timestamp: Wed Aug 23 07:07:07 2023
time-zone: UTC +03:00, Daylight Saving: +0h
type: -
duration: 45h 8m 41s
EOF
$data =~ s/^.+?:[\t ]+\K (.*[:-].*) $/'$1'/gmx;
say $data;
Now we need a literal space instead of \s
(after the first :
) since \s
matches a newline as well and that misfires on the very first line (data:
without anything following), making it search down the next line. With a literal space it can't match the newline and will abandon that first line and start the matching again from the next ^
. I like a character class for a literal space ([ ]
) for clarity, and then also add a tab, so [\t ]
.
Here we also need to limit the pattern to a line, otherwise the greedy .*
would slurp up far more. With /m
modifier the $
(and ^
) apply to the lines inside the string (without the modifier they anchor only the whole string, not lines inside). Here we also need /g
to keep going through the string, making changes.
Inside of a program the single quotes '
are not a problem as they are on the command line so now we don't need to use hex for them.
I use here doc to introduce the multiline string, with single quotes as we clearly want literal text.
Or, to avoid those subtleties and still process line by line, break the string into lines, run regex on each, then reassemble by joining with newlines (if needed)
$data = join "\n",
map { s/^.+?:\s\K (.*[:-].*)$/'$1'/xr }
split /\n+/, $data;
This eliminates possible empty lines (none in shown sample data) since I split
on all consecutive \n
(with +
). If that's undesirable use split /\n/
(no +
) and empty lines stay.
If this need not be reassembled into a multiline string -- or you'll need individual lines anyway -- then assign to an array (instead of join
-ing and assigning back to $data
).
Now we again need /r
modifier so that the block in map
returns the (changed or original) string, but not /g
(nor /m
).
\r?\n
(not a file)