2

I need to add the word "hallo" at the end of each filename before extension in the given directory. but my code produces no output. the echo statements give no output and the file names are not changed

count=""
dot="."
for file in $ls
  do
    echo $file
    count=""
    for f in $file
    do
      echo $f
      if [ $f -eq $dot ];
      then
        count=$count"hallo"
      fi
      count=$count+$f
    done
    mv $file $count
  done
3
  • Where are $ls and $file defined? Commented Aug 21, 2017 at 14:46
  • Obviously this one for file in $ls shoule be for file in "$(ls)" which is a command substituion, since there is no $ls variable defined. Moreover you should always use double quotes in all your variables. Commented Aug 21, 2017 at 14:50
  • In case you expect that for f in $file will iterate through each letter of the variable $file, then this will not happen. Commented Aug 21, 2017 at 14:54

2 Answers 2

10

With bash this can be done simplier like:

for f in *;do
echo "$f" "${f%.*}hallo.${f##*.}"
done

example:

$ ls -all
-rw-r--r-- 1 29847 29847    0 Aug 21 14:33 file1.txt
-rw-r--r-- 1 29847 29847    0 Aug 21 14:33 file2.txt
-rw-r--r-- 1 29847 29847    0 Aug 21 14:33 file3.txt
-rw-r--r-- 1 29847 29847    0 Aug 21 14:33 file4.txt
-rw-r--r-- 1 29847 29847    0 Aug 21 14:33 file5.txt 

$ for f in *;do mv -v "$f" "${f%.*}hallo.${f##*.}";done
'file1.txt' -> 'file1hallo.txt'
'file2.txt' -> 'file2hallo.txt'
'file3.txt' -> 'file3hallo.txt'

$ ls -all
-rw-r--r-- 1 29847 29847    0 Aug 21 14:33 file1hallo.txt
-rw-r--r-- 1 29847 29847    0 Aug 21 14:33 file2hallo.txt
-rw-r--r-- 1 29847 29847    0 Aug 21 14:33 file3hallo.txt

This works because ${f%.*} returns filename without extension - deletes everything (*) from the end (backwards) up to first/shortest found dot.

On the other hand this one ${f##*.} deletes everything from the beginning up to the longest found dot, returning only the extension.

To overcome the extensionless files problem as pointed out in comments you can do something like this:

$ for f in *;do [[ "${f%.*}" != "${f}" ]] && echo "$f" "${f%.*}hallo.${f##*.}" || echo "$f" "${f%.*}hallo"; done
file1.txt file1hallo.txt
file2.txt file2hallo.txt
file3.txt file3hallo.txt
file4 file4hallo
file5 file5hallo

If the file has not an extension then this will yeld true "${f%.*}" == "${f}"

Sign up to request clarification or add additional context in comments.

5 Comments

I wanted to know whats wrong with my code @GeorgeVasiliou
@Erwin you have made several incorrect guesses at shell syntax, so it's difficult to tell you exactly what's wrong with each line without knowing what the actual intent of each statement is.
This won't work for any filenames that don't have extensions. For example "filename" -> "filenamehallo.filename".
@GeorgeVasiliou You can avoid the file.file problem by changing the test to [[ "${f%.*}" != "${f}" ]].
Works perfectly, love me some Bash parameter expansion. For anyone else coming across this, bear in mind echo is there as a safety for a "dry run" showing the input and output file before - remember to replace it with mv once you've confirmed the output file matches what you want.
3

@GeorgeVasiliou has shown how to do what you want; I'll take a stab at explaining what's wrong with your original script. There are a number of places where you're using the wrong syntax for what you're trying to do.

for file in $ls

This will expand a shell variable named ls, split its contents into words, expand any wildcards found in those words into lists of matching files, and then iterate over the result. But there's no ls variable defined, so it expands to nothing, and the loop never runs. I think you're trying to iterate over the output of the ls command; the syntax for that is for file in $(ls). But you shouldn't do that, because filenames can contain spaces and tabs and linefeeds and wildcards and all manner of other funny characters that can make this iterate over... something other than a list of filenames. See the BashFAQ entry 'Why you shouldn't parse the output of ls(1)'.

What you should use instead is for file in *. The * gets expanded to a list of matching files (all visible files in the current directory), and then that list is not messed with in any way, just iterated over directly. The only potential problem is that if there aren't any matches, expansion gets skipped, and the loop runs once with file set to "*".

echo $file

When a variable is used without double-quotes around it, it'll undergo that word splitting and wildcard expansion process I described above. Usually, nothing happens. Sometimes, weird and unexpected things happen. Use double-quotes, like echo "$file".

for f in $file

The for ... in ... statement iterates over words, not characters. The shell doesn't have a particularly good way to iterate over the characters in a string, but you can iterate over the character numbers with for ((i=0; i<${#file}; i++)); do and then get the individual characters with "${file:$i:1}".

Oh, and the for (( )) and ${var:start:length} constructs are bash extensions that aren't available in all shells. If you want to use either or both in your script, be sure to start it with a bash-specific shebang line (#!/bin/bash or #!/usr/bin/env bash).

if [ $f -eq $dot ];

Again, double-quote variable references to avoid chaos when the variables contain spaces or wildcards, or are blank. Also, in a [ ] test, the -eq operator does numeric comparison not string comparison; if you give it strings that aren't valid numbers, it'll give an error. For string comparison, use = instead. Also, a semicolon isn't needed at the end of the line (well, except for a few places like the end of a case). You'd use a semicolon here if the then was on the same line, but not if it's on the next line.

count=$count+$f

The + here will be inserted as a literal character in the variable value. To append two variables, just use something like count="$count$f". (Note: this is actually one of the few places where it's safe to leave the double-quotes off. But I recommend using them anyway, because it's hard to keep track of where it's safe and where it isn't, so it's much easier to just always double-quote your variables.)

mv $file $count

Again, double-quotes. Also, when running any sort of automated bulk rename/move like this, you should always use mv -n or mv -i to keep it from overwriting files if a naming conflict (or bug or whatever) occurs.

Actually, when I'm running a mass-mv script like this for the first time, I like to add echo before the active command (e.g. echo mv -n "$file" "$count") so I can do a dry run and make sure it's going to do what I expect, before I remove the echo and run it for real.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.