ls and mv problem. File could be listed but when trying to move system says no such file

89 views Asked by At

File could be listed using ls but when trying to move using mv the system says no such file. I typed

 ~$ sudo mv "$(ls -lt /media/cache | grep ^- | head -1 | awk '{ print $9 }')" /media/cache/2

I got

mv: cannot stat 'ea9a1f08ddcf32c6e3cfea3b055d45e1e3690ca75c7bedf1ce938a2fc8bf507e.tmp': No such file or directory

Which could only means that ls could list out this ea9a1f08ddcf32c6e3cfea3b055d45e1e3690ca75c7bedf1ce938a2fc8bf507e.tmp file but mv can't move it. Where did I go wrong?

2

There are 2 answers

4
Discussian On

Your solution doesn't work because the ls pipeline outputs the filename, not the whole file path, so mv tries to find that file in the current directory (instead of /media/cache) and fails. Looking at the pipeline, you're trying to select the first file from /media/cache directory, and move it to /media/cache/2. This can be achieved using a for loop with a break:

for f in /media/cache/*; do [ -f "$f" ] && { mv "$f" /media/cache/2; break; }; done
0
Shawn On

Working with filenames in a pipeline of commands is difficult to do robustly because filenames can have almost any character in them, including newlines. And as mentioned in comments, trying to work with the output of ls is also not a good idea.

can I move 2-3 files at a time?

Some ways to work with multiple files (Or just 1 with some minor hopefully obvious tweaks):

Using zsh, you can take advantage of its glob qualifiers to control which files are returned from wildcard expansion in ways beyond just those based on the name:

files=( /media/cache/*(.) )
mv "${(@)files[1,3]}" /media/cache/2

The qualifier (.) matches just regular files, which are saved in an array. The first three elements of it are then used as arguments to mv.

In other shells, using a loop is often a better option (Similar to the one in Discussian's answer, but breaks after 3 files, not 1):

nfiles=0
for file in /media/cache/*; do
  if [ -f "$file" ]; then
    mv "$file" /media/cache/2/
    nfiles=$((nfiles + 1))
    if [ $nfiles -eq 3 ]; then
      break
    fi
  fi
done

or just punt the work to a one-liner in a more powerful language, like perl:

perl -MFile::Copy -MList::Util=head -e 'move($_, "/media/cache/2/") for head(3, grep(-f, </media/cache/*>))'

or tcl:

tclsh <<'EOF'
set files [lsort [glob -nocomplain -type f {/media/cache/*}]]
if {[llength $files]} {
  file rename -- {*}[lrange $files 0 2] /media/cache/2/
}
EOF