5

I have a number of subdirectories and I would like to move any files contained therein to the current directory using a one line Terminal command on the Mac. I know that my file names are not all unique and would like to add a suffix before the file extension.

I found a similar question Q: Move all files in sub-directories to current directory? which suggests using:

find ./*/* -type f -print0 | xargs -0 -J % mv % . 

But that's only helpful with unique filenames. For example, given the following tree:

.
├── bar
│   ├── test1.jpg
│   ├── test2.jpg
│   └── test3.jpg
├── foo
│   ├── test1.jpg
│   ├── test2.jpg
│   └── test3.jpg
├── qux
│   └── test3.jpg
└── corge
    └── test3.jpg

I would like to see a result similar to:

.
├── bar
├── foo
├── qux
├── corge
├── test1.jpg
├── test1a.jpg
├── test2.jpg
├── test2a.jpg
├── test3.jpg
├── test3a.jpg
├── test3b.jpg
└── test3c.jpg

Can anyone help?

1
  • I see now, thanks for including the example. Short of writing a small script to check whether the file exists and renaming it there, I can't think of anything better. I'd give it a shot when I'm back on a computer.
    – slhck
    Oct 25, 2013 at 20:38

3 Answers 3

1

Get a recursive list of all regular files in the current directory for each file, cut the leading "." and slashes in it to concatenate the directory and file names into one unique string, then move it to the current directory

list=$(find . -type f)
for file in $list; do
   echo -n "$file to "
   echo $file|cut -d '.' -f2-99| tr -d '/'
   mv "$file" ./"$(echo $file|cut -d '.' -f2| tr -d '/')"
done
1

Copy this script to your top directory, make it executable and run it:

#!/bin/bash

## Get a list of all files
list=$(find . -mindepth 2 -type f -print)
nr=1

## Move all files that are unique
find . -mindepth 2 -type f -print0 | while IFS= read -r -d '' file; do
    mv -n "$file" ./
done
list=$(find . -mindepth 2 -type f -print)

## Checking which files need to be renamed
while [[ $list != '' ]] ; do
   ##Remaming the un-moved files to unique names and move the renamed files
   find . -mindepth 2 -type f -print0 | while IFS= read -r -d '' file; do
       current_file=$(basename "$file")
       mv -n "$file" "./${nr}${current_file}"
   done
   ## Incrementing counter to prefix to file name
   nr=$((nr+1))
   list=$(find . -mindepth 2 -type f -print)
done
0

Unless the strict sequencial naming in your example is super-important to you, I would propose the following "keep it simple" / "quick & dirty" solution instead:

find . -type f |
while read filename; do
    basename=$(basename "$filename");
    newname=$(echo "$basename" | sed "s/^\(.*\)\(\.[^\.]\+\)$/\1(XXXX)\2/");
    printf "mv \"%s\" \"%s\";\n" "$filename" "$(mktemp -u "$newname")";
done

This only prints the commands that would be executed, so check for sanity by reviewing the output, then copy & paste into commandline, if all is to your satisfaction.

In english: find me all regular files, transform me their filenames from "test1.jpg" into form "test1(XXXX).jpg", where "XXXX" will be picked up and replaced by mktemp with random characters, then create the "mv" commandline for this action.

To decrease filename collision risk further, simply add more "X"es to it.

Have fun.

You must log in to answer this question.

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