bashfile-renamemvnosuchfileexception

mv: Cannot stat - No such file or directory


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.


Solution

  • 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