4

How can I modify a text file to do the following in 1 command?

I have a text file called Errors.txt with the following 3 lines:

Error in File /Test1/A.png
Error in File /Test2/B.jpg
Error in File /Test3/C.jpg

I want a command to do the following:

  1. Find each "Error in File" and replace it with "/mnt/HD.
  2. Add " at the end of each line.
  3. Copy each line, add a space and paste the line
  4. Replace the last 4 characters of each line with webp".
  5. Add mv at the beginning of each line.

I want the result to be like:

mv "/mnt/HD/Test1/A.png" "/mnt/HD/Test1/A.webp"
mv "/mnt/HD/Test2/B.jpg" "/mnt/HD/Test2/B.webp"
mv "/mnt/HD/Test3/C.jpg" "/mnt/HD/Test3/C.webp"
3
  • What is your end goal here? If you intend to actually run those mv commands in a shell, then inserting literal quotes is the wrong way to go about it Dec 9, 2023 at 18:05
  • This will get you partway there: sed -i 's/Error in File /\/mnt/g' <your filename> Also look at man pages and examples for the sed and awk commands.
    – Ken
    Dec 9, 2023 at 19:15
  • You need to read up on sed: grymoire.com/Unix/Sed.html
    – moo
    Dec 9, 2023 at 19:26

2 Answers 2

8

To answer your questions at face value:

1- Find Each {Error in File } and Replace it with {"/mnt}

sed 's/Error in File /"\/mnt/' Errors.txt

although you actually seem to want

sed 's/Error in File /"\/mnt\/HD/' Errors.txt

2- Add {"} at the end of each line

sed 's/$/"/' Errors.txt

3- Copy each line, add a space and paste the line

sed 's/.*/& &/' Errors.txt

4- replace the last 4 characters of each line by {webp"}

sed 's/....$/webp"/' Errors.txt

5- Add {mv } at the beginning of each line

sed 's/^/mv /' Errors.txt

So (naively) you could put that all together as

sed -e 's/Error in File /"\/mnt\/HD/' -e 's/$/"/' -e 's/.*/& &/' -e 's/....$/webp"/' -e 's/^/mv /' Errors.txt

A less piecemeal approach would be to use capture groups, like so

sed -E 's;Error in File (.*)(.{4})$;mv "/mnt/HD\1\2" "/mnt/HD\1.webp";' Errors.txt

where using ; as the sed separator allows you to include unescaped / characters in the pattern and/or replacement text.


However, if your goal is to create a set of mv commands and pass them to a shell to execute them, I'd suggest something more like

sed -n 's;^Error in File ;/mnt/HD;p' Errors.txt | 
  while IFS= read -r f; do echo mv -n -- "${f}" "${f%.*}.webp"; done

Here the sed command replaces a leading string Error in file with /mnt/HD and prints the resulting line to the standard output stream. Then the while loop reads whole lines (possibly including whitespace or other shell-special characters) and ensures they are properly quoted when passed to the mv command.

The shell substitution ${f%.*} removes the shortest dot suffix from the file - more versatile than just blindly removing the last 4 characters.

Remove the echo when you are ready to perform the mv commands for real.

3
  • Should these not be sed -i ...? Dec 10, 2023 at 11:18
  • @AdamBarnes only if the OP really, really wants to lose the original which seems like a needless risk.
    – terdon
    Dec 10, 2023 at 13:42
  • As it stands, for all but your final command, the examples you gave have no effect, which struck me as a little useless. Dec 11, 2023 at 9:40
1

You can do the whole thing in a little perl one liner:

$ perl -lne '/Error in File (.+)\.(...)$/; 
    print "mv \"/mnt/HD$1.$2\" \"/mnt/HD$1.webp\""' Errors.txt 
mv "/mnt/HD/Test1/A.png" "/mnt/HD/Test1/A.webp"
mv "/mnt/HD/Test2/B.jpg" "/mnt/HD/Test2/B.webp"
mv "/mnt/HD/Test3/C.jpg" "/mnt/HD/Test3/C.webp"
Explanation
  • perl -lne: the -n means "run the perl script given by -e on each line of the input file; the -l adds a \n to each print call (i.e. it adds a newline after printing so the next print is on a separate line).
  • /Error in File (.+)\.(...)$/;: look for Error in File followed by a space, and then look for the longest string until a . (\. will match a literal . while . alone means "any character") followed by any three characters (...) until the end of the line ($). The parentheses let you "capture" a match and then refer to it as $1 for the first set of parentheses, $2 for the second and so on. So after this, the file name is $1 and the extension is $2.
  • print "mv \"/mnt/HD$1.$2\" \"/mnt/HD$1.webp\"": print out the mv command you want.

Of course, since you're already using perl, might as well just have it rename the files directly (WARNING: this will actually rename the files):

perl -lne '/Error in File (.+)\.(...)$/; 
   rename("/mnt/HD$1.$2", "/mnt/HD$1.webp")' Errors.txt

The command above turns this:

$ tree /mnt/HD/
/mnt/HD/
├── Test1
│   └── A.png
├── Test2
│   └── B.jpg
└── Test3
    └── C.jpg

4 directories, 3 files

into this:

$ tree /mnt/HD/
/mnt/HD/
├── Test1
│   └── A.webp
├── Test2
│   └── B.webp
└── Test3
    └── C.webp

4 directories, 3 files

One more way. If your Errors.txt file is always exactly as you show, so it always has the exact string Error in File (including a space at the end) and everything after that is a file name whose extension you want t change, you could simply do:

$ sed 's/Error in File //' Errors.txt | rename -n 's/...$/webp/'
/Test1/A.png -> /Test1/A.webp
/Test2/B.jpg -> /Test2/B.webp
/Test3/C.jpg -> /Test3/C.webp

If that looks correct, run the same command with the -n option to actually rename. Or, if you really just want to print out the commands and nothing else, you could do:

$ sed 's/Error in File //' Errors.txt | 
  while IFS= read -r file; do 
    printf 'mv "%s" "%s"\n' "$file" "${file%???}"webp; 
  done
mv "/Test1/A.png" "/Test1/A.webp"
mv "/Test2/B.jpg" "/Test2/B.webp"
mv "/Test3/C.jpg" "/Test3/C.webp"

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .