I'm trying to move the last 8 files from the Documents directory to another directory, but I don't want to move them one-by-one to that specific directory. Is it possible to move them with a substitute of the tail command, but for directories instead of files? I mean I'd like to do so with something like tail -8 ./Documents | mv ./Anotherdirectory or with mv tail -8 ./Documents ./Anotherdirectory.
In fact, I'm looking for a clever way I could move the last 8 files (as listed in ls) quickly (without typing out each name) to the other directory. Any suggestions?
4 Answers
You can use for, which loops over the files in an ordered way, and allows us to avoid parsing the output of find or ls, to avoid issues with spaces and other special characters in filenames. Many thanks to @muru for improving this :)
i=0; j=$(stat ~/Documents/* --printf "%i\n" | wc -l); for k in ~/Documents/*; do if (( (j - ++i) < 8 )); then echo mv -v "$k" ~/AnotherDirectory; fi; done Test it first with echo, then remove echo to actually move the files.
As a script:
#!/bin/bash
i=0
j=$(stat ~/Documents/* --printf "%i\n" | wc -l )
for k in ~/Documents/*; do if (( (j - ++i) < 8 )); then echo mv -v -- "$k" ~/AnotherDirectory fi
doneagain, remove echo after testing to move the files for real
Explanation
i=0telling the shell to start iterating at 0j=$(stat ~/Documents/* --printf "%i\n" | wc -l )this is setting the variablejto an integer equal to the total number of files in the directory. Thanks to Serg's answer to my own question on how to count files reliably no matter what characters their names containdo if (( (j - ++i) < 8 ))for each iteration of the loop, test whether the outcome ofjminus the number of times the loop has been run is less than 8 and if it is thenmv -v -- "$k" ~/AnotherDirectorymove the file to the new directory
You can do things like this using command substitution. In Bash:
mv $(ls -d [sort options] source/* | tail -n8) destinationThe $(command) will run whatever is enclosed in it and substitute the text output into the outer command. That ls command will print the path to each file in the source directory sorted according the the flags you specify, one per line, so tail can just take the last few. Thus the above would expand to
mv source/file1 source/file2 source/file3 ... source/file8 destinationYou can just run ls -d [sort options] source/* | tail -n8 to see what files it will copy.
Depending on how you're ordering the output of ls and the file naming, you may be able to do what you're looking for more simply just using some variant of mv source/name_* destination to copy everything starting with "name_" to the destination directory.
EDIT:The above breaks when there are spaces in the file names. A more complicated alternative that addresses this would be
ls -d1 [sort options] source/* | tail -n8 | tr '\n' '\0' | xargs --null mv -t destinationthough it still makes use of parsing ls output to get sorting in any order that isn't alphabetical.
similar to t-mager's suggestion, you can use ls sort options and grab the last 8 files into a list and these are your files:
1 10 2 3 4 5 6 7 8 9
use the following command:for n in `ls|tail -8`; do mv $n ~/temp; done
I find that this works too: ls -1 [sort options] | tail -n2 | xargs -I {} -n 1 echo "mv \"{}\" ../t2/"
Of course, the echo has to be removed: ls -1 | tail -n2 | xargs -I {} -n 1 mv "{}" ../t2/
The "{}" can be replaced with '{}'Actually, as a final experiment, removing the (double) quotes works too. Surprising, but tested.