0

My first file screen.txt contains single letters, such as:

d
m
a
o

The second file beta.txt contains a lot of lines:

cvvbbe
etgjiua
qwrfggo

The third file gama.sh is a shell script

I need to loop over the beta.txt file in the following way:

  • replace the last letter of every line in beta.txt with the first letter from screen.txt
  • save beta.txt
  • run gama.sh
  • replace the last letter of every line in beta.txt with the second letter from screen.txt
  • save beta.txt
  • run gama.sh
  • and so on
4
  • 3
    What did you try so far? Commented Aug 20, 2022 at 16:39
  • 1
    Where exactly should the combined input be written? Do you need to modify beta.txt in place, or does gama.sh read the concatenated lines via standard input? Commented Aug 20, 2022 at 16:49
  • thank you for caring, i want to modify beta.txt without chang place (replace last letter in every line by first line in screen.txt, then by 2nd letter... Commented Aug 20, 2022 at 17:16
  • thank you for caring, i change the question to make it easy, and i don't want to add gama.sh. Commented Aug 20, 2022 at 18:22

2 Answers 2

2

Using any awk:

$ cat gama.sh
#!/usr/bin/env sh

awk '
    NR==FNR {
        beta[++nr] = substr($0,1,length($0)-1)
        next
    }
    {
        for ( i=1; i<=nr; i++ ) {
            print beta[i] $0 > ARGV[1]
        }
        print "" > ARGV[1]
    }
' "$@"

$ ./gama.sh beta.txt screen.txt

$ cat beta.txt
cvvbbd
etgjiud
qwrfggd

cvvbbm
etgjium
qwrfggm

cvvbba
etgjiua
qwrfgga

cvvbbo
etgjiuo
qwrfggo

The above assumes the contents of beta.txt aren't too huge to fit in memory, otherwise if the contents of screen.txt fit in memory then:

$ cat gama.sh
#!/usr/bin/env sh

tmp=$(mktemp) &&
awk '
    BEGIN { OFS="\t" }
    NR==FNR {
        screen[++nr] = $0
        next
    }
    {
        $0 = substr($0,1,length($0)-1)
        for ( i=1; i<=nr; i++ ) {
            print i, FNR, $0 screen[i]
        }
    }
    END {
        for ( i=1; i<=nr; i++ ) {
            print i, FNR+1
        }
    }
' "$@" |
sort -k1,2n |
cut -f3- > "$tmp" &&
mv -- "$tmp" "$2"

$ ./gama.sh screen.txt  beta.txt

$ cat beta.txt
cvvbbd
etgjiud
qwrfggd

cvvbbm
etgjium
qwrfggm

cvvbba
etgjiua
qwrfgga

cvvbbo
etgjiuo
qwrfggo

That second script applies the DSU (Decorate/Sort/Undecorate) idiom to produce output lines in the desired order, see https://stackoverflow.com/questions/71691113/how-to-sort-data-based-on-the-value-of-a-column-for-part-multiple-lines-of-a-f/71694367#71694367 for more details on that.

0

You can run sed in a loop, like this:

 $ while read letter; do 
    printf '%s\n\n' "$(sed "s/.$/$letter/" beta.txt)";
   done < screen.txt > tmpFile && mv tmpFile beta.txt

$ cat beta.txt
cvvbbd
etgjiud
qwrfggd

cvvbbm
etgjium
qwrfggm

cvvbba
etgjiua
qwrfgga

cvvbbo
etgjiuo
qwrfggo

If you want it as a script, you can just save this as gamma.sh:

#!/bin/sh

tmpFile=$(mktemp)
while read letter; do 
    printf '%s\n\n' "$(sed "s/.$/$letter/" beta.txt)";
done < screen.txt > "$tmpFile" && mv tmpFile 

Note that this will be extremely slow and impractical for large files. It's fine for the dummy example you gave, but if you need to deal with a few thousand or even hundreds of lines, you should use another language. For example, in Perl:

#!/usr/bin/perl
use strict;
use warnings;

my @letters;
open(my $letterFile, '<', "$ARGV[0]") or
    die("Failed to open letter file '$ARGV[0]':$!\n");
while (my $line = <$letterFile>) {
  chomp($line);
  $line =~ /(.)\s*$/;
  push @letters, $1;
}
close($letterFile);

open(my $dataFile, '<', "$ARGV[1]") or
    die("Failed to open data file '$ARGV[1]':$!\n");

my @data = <$dataFile>;
my $c = 0;
foreach my $letter (@letters) {
  print "\n" if $c;
  foreach my $line (@data) {
    $line =~ s/.$/$letter/;
    print "$line";
  }
  $c++;
}
close($dataFile);

If you save the script above as foo.pl, you can do:

$ perl foo.pl screen.txt beta.txt
cvvbbd
etgjiud
qwrfggd

cvvbbm
etgjium
qwrfggm

cvvbba
etgjiua
qwrfgga

cvvbbo
etgjiuo
qwrfggo

Note that this will store all of beta.txt in memory which might be an issue for huge files. If that is a problem, then you will need a different approach but that's beyond the scope of the current question.

6
  • That first script won't produce the output you say it does (and the output you say it produces has an extra block on the end but that's not really relevant since it won't do that anyway). Commented Aug 21, 2022 at 13:22
  • @EdMorton I'm on mobile at the moment, but the output was copy pasted. Granted, I wrote this while drinking the first coffee of the day, so it's even more likely than usual that I've made a mistake somewhere, but that's the output I got and I don't see any obvious mistakes. What's the problem? I'm guessing I hadn't cleared the output file before rerunning while testing, as an excuse for the extra block. Commented Aug 21, 2022 at 19:48
  • Ugh, that problem was me running it after I had run my script. Can't overstate how much I hate questions that require changing the input file so you can't easily test multiple times! Anyway, the problem I see now using the original input file is the same as you show - 1 extra duplicate block at the end. I don't know why, honestly the script seems kinda convoluted for all that it does so it's a little hard to understand given as much effort as I'm willing to put into it. Commented Aug 21, 2022 at 20:35
  • 1
    Fixed now, @EdMorton, thanks. And yeah, but I find printf '\n\n' clearer than the empty echo. Commented Aug 22, 2022 at 12:58
  • 1
    You're welcome. IMHO printf '%s\n\n' "$(sed "s/.$/$letter/" beta.txt)" is less clear and simple than sed "s/.$/$letter/" beta.txt; printf '\n\n' and I expect it'd be slower. Commented Aug 22, 2022 at 13:01

You must log in to answer this question.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.