2

I am renaming some files.

This works:
ls | while IFS= read -r line; do name=$(echo $line | sed -e 's/\(.*\)/\1.jpg/') && mv $line $name; done

Which is okay but I'd like to make it more concise, like:
ls | while IFS= read -r line; do name=$(sed -e 's/\(.*\)/\1.jpg/' $line) && mv $line $name; done

The latter example and various similar attempts pass the file into sed rather than the value of the variable.

Using ˋ$lineˋ and $($line) both result in an attempt to execute the file. While variations of "$line", 'line' with and without <, all lead to the file be read in. What is going on here? Can the $line variable be used directly with sed for this purpose?

  • 1
    You can use a here string <<< "$line" - however, you should really rethink your entire approach. Do you really need anything more than for f in *; do mv -- "$f" "$f.jpg"; done? – steeldriver Jan 10 '17 at 14:52
  • Are you attempting to append ".jpg" to every existing filename? Or replace the current extension with ".jpg"? Either way, there are much simpler ways to go about it - see http://unix.stackexchange.com/questions/24107/how-can-i-rename-a-lot-of-files-using-a-regex – JigglyNaga Jan 10 '17 at 14:55
  • How can conciseness be worth anything with such an ugly command (in both cases)? Who cares if their command is 4 bytes longer, especially if it can be written in a script? – Julie Pelletier Jan 10 '17 at 14:58
  • There are two reasons for this question, 1. I've learned something; 2. In my opinion <<< "$line" is more readable than echo $line | sed. – dies_felices Jan 10 '17 at 15:50
  • 1
    prename 's/$/.jpg/' * – JJoao Jan 10 '17 at 16:10

2 Answers2

7

It looks like you're just wanting to append .jpg to the end of all the names of the files in the current directory. If that's the case, then there are easier ways:

for f in ./*; do
  mv -- "$f" "$f.jpg"
done

The -- makes sure that filenames with an initial dash (-) are not interpreted as options for mv. This is not an issue here though as we're also making sure that $f always has the path ./ in the beginning through using ./* in the loop.

The output from ls is for reading by eyes, and is not generally suitable for sending into a while-read-loop like you're doing.

And yes, in your second example (sed -e 's/\(.*\)/\1.jpg/' $line), the contents of the file will be read by sed. With Bash, you could do sed '...' <<<"$line" instead. The <<<"string" construct is a Bash extension (also available in at least ksh and zsh) and is called "here-string", and it will pass the string on standard input to the command.

Kusalananda
  • 333,661
1

You can use a Here String with <<< syntax:

ls | while IFS= read -r line
do
  name=$(sed 's/\(.*\)/\1.jpg/' <<< $line) && mv "$line" "$name"
done

Be aware that it's not considered a good idea to parse the output of ls (nor to use unquoted variables).

There is also a tool name rename (in case goal is simply to rename file):

rename -n 's/$/.jpg/' *

Remove the -n flag to change from preview to actual renaming.

Toby Speight
  • 8,678
Archemar
  • 31,554
  • My example above is a part of larger script which has a more complex sed pattern.

    Also, the use of the ls just for this example for ease of use etc.

    – dies_felices Jan 10 '17 at 15:42
  • 3
    Note that that loop approach still fails if there are file names that start with - or contain newline characters or sequences of bytes not forming valid characters. See @Kusalananda's answer for a more correct approach. – Stéphane Chazelas Jan 10 '17 at 17:04