Score:4

What command can I use to move files from a directory to another, starting from smaller size to bigger size?

ci flag

I am looking for a a Linux command that can move files from one directory to another directory in ascending order of file sizes, e.g. first files of size 1 Mb, then 1.2 Mb and then 1.3 Mb, etc.

Score:3
pa flag

This is a multi-line one-liner to be run in the source dir:

find -type f -printf '%s/%p\0' | # get paths with sizes together into safe zero-delimited lines
    sort -zn | # do numeric sort (ascending)
    cut -zd/ -f2- | #cut the sizes down
    xargs -0 cp -v --parents --target-directory="../B/" # do the copy

Notes:

  • do not forget to cd path/to/source/dir.
  • replace "../B/" with a relative or an absolute path to the destination dir.
  • the file names can contain any characters, including spaces, newlines, $%^<>"" and other tricky stuff.
  • it preserves paths, creating necessary sub-folders on the destination.
  • one can omit -v to make cp silent.
  • the metadata is not preserved, use cp's --preserve=all for that.
  • this script COPIES, not moves files. The reason is being that:
  1. There is no mv --parents command and I was lazy to emulate it.
  2. Inside the one file system you can simply rename the whole source dir to destination, which makes files to be "moved" instantaneously. However, the move to the other file system is done trough copy+remove source sequence anyways.

Here is how I understand your dataset/create test data:

# making test dirs
mkdir test/{,A,B}
cd test/A
mkdir sub{1..4} sub1/sub{1..2}
# making test file of random sizes
find -mindepth 1 -type d -exec sh -c 'cd "$1"; x=${RANDOM}0; dd if=/dev/urandom of=${RANDOM}.${x}.bin count=${x}' - {} \;
$ tree -s .. # review
..
├── [          0]  A
│   ├── [          0]  sub1
│   │   ├── [   89502720]  5334.174810.bin
│   │   ├── [          0]  sub1
│   │   │   └── [  126310400]  24545.246700.bin
│   │   └── [          0]  sub2
│   │       └── [  124303360]  17288.242780.bin
│   ├── [          0]  sub2
│   │   └── [  149780480]  22029.292540.bin
│   ├── [          0]  sub3
│   │   └── [    9246720]  19263.18060.bin
│   └── [          0]  sub4
│       └── [  110320640]  31098.215470.bin
└── [          0]  B

8 directories, 6 files

Listing:

$ find -type f -printf '%s/%h/%f\0' | sort -zn | cut -zd/ -f2- | tr '\0' '\n' | xargs -l ls -lh
-rw-r--r-- 1 user group 8.9M Aug  7 14:31 ./sub3/19263.18060.bin
-rw-r--r-- 1 user group 86M Aug  7 14:30 ./sub1/5334.174810.bin
-rw-r--r-- 1 user group 106M Aug  7 14:31 ./sub4/31098.215470.bin
-rw-r--r-- 1 user group 119M Aug  7 14:30 ./sub1/sub2/17288.242780.bin
-rw-r--r-- 1 user group 121M Aug  7 14:30 ./sub1/sub1/24545.246700.bin
-rw-r--r-- 1 user group 143M Aug  7 14:31 ./sub2/22029.292540.bin

How it works:

$ find -type f -printf '%s/%p\0' | # get paths with sizes together into safe zero-delimited lines
    sort -zn | # do numeric sort (ascending)
    cut -zd/ -f2- | #cut the sizes down
    xargs -0 cp -v --parents --target-directory="../B/" # do the copy
./sub3 -> ../B/./sub3
'./sub3/19263.18060.bin' -> '../B/./sub3/19263.18060.bin'
./sub1 -> ../B/./sub1
'./sub1/5334.174810.bin' -> '../B/./sub1/5334.174810.bin'
./sub4 -> ../B/./sub4
'./sub4/31098.215470.bin' -> '../B/./sub4/31098.215470.bin'
./sub1/sub2 -> ../B/./sub1/sub2
'./sub1/sub2/17288.242780.bin' -> '../B/./sub1/sub2/17288.242780.bin'
./sub1/sub1 -> ../B/./sub1/sub1
'./sub1/sub1/24545.246700.bin' -> '../B/./sub1/sub1/24545.246700.bin'
./sub2 -> ../B/./sub2
'./sub2/22029.292540.bin' -> '../B/./sub2/22029.292540.bin'
Score:3
cn flag

If you are talking about files in a single directory to be moved to another single directory, you can use the ls command which can sort files by size. The following script will move files from source directory (given as the first parameter) to the destination directory (given as the second parameter) in the order of increasing file size:

#!/usr/bin/env bash
src="$1"
dst="$2"
if [[ ! -d "$src" ]] ; then
  echo "Source directory $src does not exist!"
  exit 1
fi
if [[ ! -d "$dst" ]] ; then
  echo "Destination directory $dst does not exist!"
  exit 2
fi
cd "$src" || exit
ls -arS | while read -r f ; do
  if [[ -f "$f" ]] ; then
    echo mv "$f" "$dst"
  fi
done

You can remove the echo command once you are satisfied that the script works OK.

BeastOfCaerbannog avatar
ca flag
Could you also add some instructions for how to make the script executable and also how to use it? It looks like it uses the first two arguments as the `src` and `dst` variables, so I suppose that it should be run as `script src_dir dst_dir`. While this might be obvious for someone familiar with Bash, it isn't for most users. ;)
Raffa avatar
jp flag
Why all that if you're going to use `ls` ... `ls -rS | xargs -I {} mv {} /path/to/target/` or even `ls -rS | xargs -I {} bash -c '[ -f "$1" ] && mv "$1" /path/to/target/' bash {}` should do ... I think.
terdon avatar
cn flag
Note that this will fail on file names that contain `\n`.
Score:3
om flag

You can use find.

find . -size +1M will find every file over 1MB in the present directory, recursively.

Adding -exec we can move files:

find . -type f -size +1M -exec mv "{}" large_files/ \;

This can be made into a loop using bash scripting:

for size in $(seq 2 -0.1 1)
do
mkdir -p "${size}"_files
find . -type f -size +"${size}"M -exec mv {} large_files/ \;
done

Here seq will generate the numbers between 2 and 1, in increments of 0.1, e.g. 2, 1.9 etc. We have to do it reverse order, as files bigger than 1MB will also match a 2MB file, thus use a negative increment.

mkdir will create a directory for each size, and find will find and move files into the directory. The -p ensures no error will be printed if the directory already exists.

Score:3
jp flag

In the Z Shell(zsh), utilizing its Glob Qualifiers, you can do something like this(from within the source directory):

With zsh's builtin function zmv:

% autoload zmv
%
% zmv -Q -n -- '*(.oL)' '/path/to/target/$f'

Notice: The option -n is for a dry-run ... When satisfied with the output, substitute it with -v(for verbosity) for actual moving to happen.

or with external /bin/mv(First, see notice below for dry-run):

printf '%s\0' *(.oL) | xargs -0 mv -nv -t /path/to/target/ --

or to move one file at a time:

printf '%s\0' *(.oL) | xargs -0 -I {} mv -nv -- {} /path/to/target/

Notice: For a dry-run you can use something like this:

printf '%s\0' *(.oL) | xargs -0 stat --printf='Name: %n Size: %s\n'

Where in *(.oL):

  • . means plain files.
  • o means sort ascending.
  • L means by the size (length) of the files.
mangohost

Post an answer

Most people don’t grasp that asking a lot of questions unlocks learning and improves interpersonal bonding. In Alison’s studies, for example, though people could accurately recall how many questions had been asked in their conversations, they didn’t intuit the link between questions and liking. Across four studies, in which participants were engaged in conversations themselves or read transcripts of others’ conversations, people tended not to realize that question asking would influence—or had influenced—the level of amity between the conversationalists.