Search code examples
shellzsh

why does zsh `for` loop error with `command not found: filename.txt`? despite filename.txt being a parameter and not a command


The following shell history shows two attempts to rename files, the second attempt worked. But the first attempt did not and I have no idea why...

❯ ls
step-10.txt  step-12.txt  step-14.txt  step-16.txt  step-18.txt  step-1.txt  step-3.txt  step-5.txt  step-7.txt  step-9.txt
step-11.txt  step-13.txt  step-15.txt  step-17.txt  step-19.txt  step-2.txt  step-4.txt  step-6.txt  step-8.txt
❯ for f in *.txt; do
newname=$(echo $f | sed 's/txt/md/')\
mv $f $newname
done
zsh: command not found: step-10.txt
zsh: command not found: step-11.txt
zsh: command not found: step-12.txt
zsh: command not found: step-13.txt
zsh: command not found: step-14.txt
zsh: command not found: step-15.txt
zsh: command not found: step-16.txt
zsh: command not found: step-17.txt
zsh: command not found: step-18.txt
zsh: command not found: step-19.txt
zsh: command not found: step-1.txt
zsh: command not found: step-2.txt
zsh: command not found: step-3.txt
zsh: command not found: step-4.txt
zsh: command not found: step-5.txt
zsh: command not found: step-6.txt
zsh: command not found: step-7.txt
zsh: command not found: step-8.txt
zsh: command not found: step-9.txt
↵ Error. Exit status 127.
❯ for f in *.txt; do
mv $f $(echo $f | sed 's/txt/md/')
done
❯ ls
step-10.md  step-12.md  step-14.md  step-16.md  step-18.md  step-1.md  step-3.md  step-5.md  step-7.md  step-9.md
step-11.md  step-13.md  step-15.md  step-17.md  step-19.md  step-2.md  step-4.md  step-6.md  step-8.md

Why does it work when I place $(echo $f | sed 's/txt/md/') on the line with mv but not when it's assigned to the variable? Why does it say command not found: $f when my loop is providing $f as an argument to the mv command?


Solution

  • That backslash means it's evaluating

    newname=$(echo $f | sed 's/txt/md/')mv $f $newname
    

    which of course treats the filename as the name of a command to run (The mv is part of the newname variable)

    You don't need sed at all here; just use parameter expansion:

    for f in *.txt; do
        mv "$f" "${f/%txt/md}"
    done
    

    There are also several different programs named rename (prename, rename.ul, etc. depending on OS) that can mass rename files; check man pages for which ones (if any) you have installed and usage.