mv: Cannot stat - No such file or directory

jacob picture jacob · Sep 1, 2017 · Viewed 7.3k times · Source

I have piped the output of ls command into a file. The contents are like so:

[Chihiro]_Grisaia_no_Kajitsu_-_01_[1920x816_Blu-ray_FLAC][D2B961D6].mkv
[Chihiro]_Grisaia_no_Kajitsu_-_02_[1920x816_Blu-ray_FLAC][38F88A81].mkv
[Chihiro]_Grisaia_no_Kajitsu_-_03_[1920x816_Blu-ray_FLAC][410F74F7].mkv

My attempt to rename these episodes according to episode number is as follows:

cat grisaia | while read line; 
   #get the episode number
   do EP=$(echo $line | egrep -o  "_([0-9]{2})_" | cut -d "_" -f2)
   if [[ $EP ]]
      #escape special characters
      then line=$(echo $line | sed 's/\[/\\[/g' | sed 's/\]/\\]/g')
      mv "$line" "Grisaia_no_Kajitsu_${EP}.mkv"
   fi
done

The mv commands exit with code 1 with the following error:

mv: cannot stat '\[Chihiro\]_Grisaia_no_Kajitsu_-01\[1920x816_Blu-ray_FLAC\]\[D2B961D6\].mkv': No such file or directory

What I really don't get is that if I copy the file that could not be stat and attempt to stat the file, it works. I can even take the exact same string that is output and execute the mv command individually.

Answer

whoan picture whoan · Sep 1, 2017

If you surround your variable ($line) with double quotes (") you don't need to escape those special characters. So you have two options there:

  1. Remove the following assignation completely:

    then # line=$(echo $line | sed 's/\[/\\[/g' | sed 's/\]/\\]/g')`
    

    or

  2. Remove the double quotes in the following line:

    mv $line "Grisaia_no_Kajitsu_${EP}.mkv"
    

Further considerations

  1. Parsing the output of ls is never a good idea. Think about filenames with spaces. See this document for more information.

  2. The cat here is unnecessary:

    cat grisaia | while read line; 
        ...
    done
    

    Use this instead to avoid an unnecessary pipe:

    while read line; 
        ...
    done < grisaia
    

Why is good to avoid pipes in some scenarios? (answering comment)

Pipes create subshells (which are expensive), and you can also make some mistakes as the following:

last=""

cat grisaia | while read line; do
    last=$line
done

echo $last # surprise!! it outputs an empty string

The reason is that $last inside the loop belongs to another subshell.

Now, see the same approach wothout pipes:

while read line; do
    last=$line
done < grisaia

echo $last # it works as expected and prints the last line