Say i have a directory of filenames:

  • file_010.ext
  • file_011.ext
  • ...
  • file_99.ext

What's the simplest way to take each file and randomly rename it to any other name in the directory, without there being any duplicates, such that on inspection the directory looks to be the same but the content has been reshuffled?

  • 4
    Please tell us why you want to do this? – sudodus Mar 14 at 15:46
  • The software I'm using pairs data according to names, I want to set up a "control" environment where the paired data doesn't match to observe how the model will perform. – PedsB Mar 14 at 15:49
  • Must all files move or would it be OK is one or a few files would be the same in the end? It would allow using a simpler shuffle process. – sudodus Mar 14 at 15:58
  • 1
    You should edit the question to show what you've already tried and/or researched. See How to Ask. – Anthony Geoghegan Mar 14 at 15:58
  • 1
    I'm at the mercy of the software, it always pairs the files in the two directories according to naming conventions: Hence why I want to rename within the scope of files in the directory. – PedsB Mar 14 at 16:46

Here's one using shuf from GNU coreutils:

paste <(printf "%s\n" *) <(printf "%s\n" * | shuf) |
  while IFS=$'\t' read -r from to; do mv -- "$from" "$to.new"; done

for f in *.new; do mv -- "$f" "${f%.new}"; done 

printf "%s\n" * creates a list of the filenames, shuf shuffles them. paste combines the two lists so that there is a list of filenames in order in one column, and in a shuffled order in another column. (You could build the lists in temporary files if you don't want to use process substitution as done above; also, replace IFS=$'\t' with IFS=$(printf '\t') if your shell doesn't support $''.)

Then this is just fed to the while read loop and we rename the files based on the list, adding a suffix to not overwrite any of the old files. The second loop just removes the suffixes.

The above assumes the filenames don't contain tabs or newlines.

(You could implement the shuffle algorithm manually in the shell, but I think it gets a bit messy.)

  • It assumes file names don't contain space, tab or newline characters (assuming the default value of $IFS). – Stéphane Chazelas Mar 14 at 19:00

Going with that symbolic link idea of ctrl-alt-delor, using shuf from GNU coreutils (this code also uses readlink -f from GNU coreutils, to get the absolute pathname of a given file, for the purpose of creating the symbolic link):

#!/bin/sh -e

shufdir=shufdir  # directory at this path will be deleted and recreated

rm -rf "$shufdir"
mkdir -p "$shufdir"

printf '%s\n' "$@" | shuf |
while read -r fname; do
    ln -s "$( readlink -f "$1" )" "$shufdir/$fname"

The script takes the filenames to shuffle on its command line. It creates $shufdir as a subdirectory and proceeds to shuffle all the given filenames. The shuffled filenames are then symbolically linked under $shufdir to the original list of filenames given on the command line.

The code assumes pathnames containing no newlines.


$ ls -l
total 4
-rw-r--r--  1 kk  wheel    0 Mar 14 18:13 file-1
-rw-r--r--  1 kk  wheel    0 Mar 14 18:13 file-10
-rw-r--r--  1 kk  wheel    0 Mar 14 18:13 file-2
-rw-r--r--  1 kk  wheel    0 Mar 14 18:13 file-3
-rw-r--r--  1 kk  wheel    0 Mar 14 18:13 file-4
-rw-r--r--  1 kk  wheel    0 Mar 14 18:13 file-5
-rw-r--r--  1 kk  wheel    0 Mar 14 18:13 file-6
-rw-r--r--  1 kk  wheel    0 Mar 14 18:13 file-7
-rw-r--r--  1 kk  wheel    0 Mar 14 18:13 file-8
-rw-r--r--  1 kk  wheel    0 Mar 14 18:13 file-9
-rw-r--r--  1 kk  wheel  247 Mar 14 18:39 script.sh
$ sh script.sh file-*
$ ls -l shufdir/
total 0
lrwxr-xr-x  1 kk  wheel  31 Mar 14 18:39 file-1 -> /tmp/shell-yash.WkdNi1GD/file-8
lrwxr-xr-x  1 kk  wheel  31 Mar 14 18:39 file-10 -> /tmp/shell-yash.WkdNi1GD/file-9
lrwxr-xr-x  1 kk  wheel  31 Mar 14 18:39 file-2 -> /tmp/shell-yash.WkdNi1GD/file-4
lrwxr-xr-x  1 kk  wheel  31 Mar 14 18:39 file-3 -> /tmp/shell-yash.WkdNi1GD/file-1
lrwxr-xr-x  1 kk  wheel  31 Mar 14 18:39 file-4 -> /tmp/shell-yash.WkdNi1GD/file-3
lrwxr-xr-x  1 kk  wheel  31 Mar 14 18:39 file-5 -> /tmp/shell-yash.WkdNi1GD/file-2
lrwxr-xr-x  1 kk  wheel  31 Mar 14 18:39 file-6 -> /tmp/shell-yash.WkdNi1GD/file-6
lrwxr-xr-x  1 kk  wheel  31 Mar 14 18:39 file-7 -> /tmp/shell-yash.WkdNi1GD/file-7
lrwxr-xr-x  1 kk  wheel  31 Mar 14 18:39 file-8 -> /tmp/shell-yash.WkdNi1GD/file-5
lrwxr-xr-x  1 kk  wheel  32 Mar 14 18:39 file-9 -> /tmp/shell-yash.WkdNi1GD/file-10

There is nothing that stops you from copying or moving the files to the new directory, or to use hard links instead of symbolic links (if the $shufdir directory lives on the same partition as the original files). You just need to change the line with the ln -s command.


With zsh:

zmodload zsh/files
autoload zargs zmv


# shuffle the list by using our custom "rand" order function

# append a ".new" suffix

# rename files
zargs -l2 -- ${files:^new_files} -- mv --

# remove the .new extension once all the files are renamed
zmv '(*.ext).new' '$1'

Same idea as @ilkkachu's but doesn't make assumptions on what characters file names may contain and doesn't need any external utility (the zsh/files brings in a builtin mv command).



This is not the shortest way to solve the problem, but I think it is rather easy to understand.

It is a good idea to make a tarball with the original files

tar -cvzf backup.tar.gz file*

to be able to restore them after the test (or to repeat the test)

tar -xvf backup.tar.gz
  • I use a few temporary files
  • in order to build a file with copy commands,
  • that shuffle the file names, "$tmpdir"/tg3.
  • The original files remain while running these commands, and
  • finally they are overwritten.
  • The process is complete and the temporary directory can be removed.

The shellscript shuffler


tmpdir=$(mktemp -d)

ls -1 file* > "$tmpdir"/orig  # list the file names in a file
sed -i -e 's/^/"/' -e 's/$/"/' "$tmpdir"/orig  # quote to allow special chars

cd "$tmpdir"

shuf orig > shuf           # shuffle the names

paste orig shuf > tg1      # build commands ...
sed 's/^/cp /' tg1 > tg2   # build commands
sed 's/"$/xtmp"/' tg2 > tg3  # make temporary names
#less tg3
cd "$curdir"

bash "$tmpdir"/tg3         # copy the files to shuffled temporary names
rename -f 's/xtmp$//' file*  # overwrite the original files

rm -r "$tmpdir"

Demo example

  • Create/Select a directory

  • Create test files with content matching the file names

    for ((i=1;i<100;i++));do echo $i>file_$i;done
  • Save the files in a tarball

    tar -cvzf backup.tar.gz file*
  • Copy the shellscript from here to a text editor and save it with the name shuffler in the same directory.

  • Make the shellscript executable

    chmod +x shuffler
  • Do it


Now you can check that the files are really shuffled with the following command line (only valid for this particular test example)

$ for ((i=1;i<100;i++));do echo -n "file_$i: ";j=$(cat file_$i);if [ "$i" == "$j" ]; then echo "$j same";else echo "$j";fi;done
file_1: 98
file_2: 45
file_3: 1
file_4: 5
file_5: 93
file_6: 31
file_7: 52
file_8: 84
file_9: 57
file_10: 44
file_11: 2
file_12: 92
file_13: 32
file_14: 12
file_15: 38
file_16: 10
file_17: 64
file_18: 75
file_19: 30
file_20: 68
file_21: 87
file_22: 26
file_23: 36
file_24: 53
file_25: 50
file_26: 51
file_27: 41
file_28: 49
file_29: 21
file_30: 17
file_31: 61
file_32: 73
file_33: 9
file_34: 16
file_35: 55
file_36: 85
file_37: 24
file_38: 83
file_39: 59
file_40: 18
file_41: 20
file_42: 29
file_43: 66
file_44: 82
file_45: 56
file_46: 48
file_47: 71
file_48: 79
file_49: 14
file_50: 86
file_51: 60
file_52: 43
file_53: 22
file_54: 54 same
file_55: 19
file_56: 89
file_57: 28
file_58: 34
file_59: 77
file_60: 88
file_61: 58
file_62: 4
file_63: 96
file_64: 94
file_65: 39
file_66: 69
file_67: 65
file_68: 7
file_69: 90
file_70: 6
file_71: 8
file_72: 47
file_73: 80
file_74: 25
file_75: 97
file_76: 33
file_77: 13
file_78: 15
file_79: 81
file_80: 37
file_81: 42
file_82: 78
file_83: 74
file_84: 3
file_85: 95
file_86: 76
file_87: 40
file_88: 70
file_89: 99
file_90: 27
file_91: 23
file_92: 11
file_93: 91
file_94: 62
file_95: 35
file_96: 63
file_97: 46
file_98: 72
file_99: 67

Scroll down and notice that file_54 is the same. All other files have new content. Like with a deck of cards, you can shuffle twice or three times ...

You can also reset the file names from the tarball and do it again,and there will be new results every time because shuf will get new start values.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.