7

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
  • Please write in English what your end goal is. It's not clear from your question. To move all files from one directory to another: mv dir1/* dir2.
    – darksky
    Nov 15, 2016 at 17:43
  • do you mean something like... the last 8 files shown by ls?
    – Zanna
    Nov 15, 2016 at 17:47
  • @darksky It's because many files in the Documents directory are mixed up and not well organised. So, for instance, I have created Curriculum_Vitae directory in the Documents directory (i.e. ./Documents/Curriculum_Vitae) and I want to put all files related to the subject in that directory. However, there are a lot of those file and I don't want to move them one-by-one. Nov 15, 2016 at 17:49
  • 1
    @Zanna Yes, exactly!! Nov 15, 2016 at 17:49

4 Answers 4

7

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
done

again, remove echo after testing to move the files for real

Explanation

  • i=0 telling the shell to start iterating at 0
  • j=$(stat ~/Documents/* --printf "%i\n" | wc -l ) this is setting the variable j to 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 contain
  • do if (( (j - ++i) < 8 )) for each iteration of the loop, test whether the outcome of j minus the number of times the loop has been run is less than 8 and if it is then
  • mv -v -- "$k" ~/AnotherDirectory move the file to the new directory
6
  • 1
    [[ $((j-$((++i)))) -lt 8 ]] == (( (j - ++i) < 8 ))
    – muru
    Nov 16, 2016 at 1:38
  • @muru I will remove some parentheses & thanks, but < will go wrong if there are ten files or more because it means sorts before not less than and 10 will sort before 8.
    – Zanna
    Nov 16, 2016 at 6:54
  • 1
    Nope. (( )) is arithmetic context. < compares integers in that context, 8 < 10.
    – muru
    Nov 16, 2016 at 6:55
  • @muru oh I am awake now (5 and a half hours behind & more haha) obviously you're right & thank you
    – Zanna
    Nov 16, 2016 at 7:03
  • i have a folder with a huge amount of files, ls would not work. This command also does not work as requested. I am using it and kill it after a while as it moves all files and does not stop. I have managed to move hundreds and then I have to CTRL + C to avoid flooding the other folder
    – Bizmate
    Dec 4, 2019 at 19:38
3

You can do things like this using command substitution. In Bash:

mv $(ls -d [sort options] source/* | tail -n8) destination

The $(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 destination

You 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 destination

though it still makes use of parsing ls output to get sorting in any order that isn't alphabetical.

0
1

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

3
  • 1
    Not a good idea
    – Zanna
    Nov 17, 2016 at 22:21
  • @Zanna, why not? besides the original backticks problem, this command simply moves that last 8 files form 1 directory to another and is easy to understand.
    – Eric Formo
    Nov 21, 2016 at 17:40
  • It will break if there are spaces, newlines etc in filesnames. See the link for why I spent so much effort on my own answer when parsing ls is much more obvious...
    – Zanna
    Nov 21, 2016 at 17:47
0

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.

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .