Take the 2-minute tour ×
Super User is a question and answer site for computer enthusiasts and power users. It's 100% free, no registration required.

I have a directory filled with various files, differently named (i.e. not named with a specific pattern) and with different extensions, and I want to put each file in a newly created subdirectory named after each file; what's the best way to accomplish that from the command line under Linux?
This sounds a bit confusing, let me make and example: let's call a random file in the directory ./filename.ext, I'd like to put it in a subdirectory called ./filename so that effectively the new path will be ./filename/filename.ext.

I did some trials with the following steps:

  • I've created some test files, from file-1.txt to file-9.txt, inside a test directory with for num in {1..9}; do touch file-$num.txt; done
  • I've created a directory named after each filename with for name in $(eval ls | cut -b 1-6); do mkdir $name; done
  • I've moved the source files in their respective directories with for name in $(eval ls | grep -e ".txt" | cut -b 1-6); do mv $name.txt $name/$name.txt; done

As you can see, this solution works, but its huge drawback is that it works just in this case, with files of that extact filename lenght and with that particular extension. If I have some files with different names and extensions, this would be of no use at all. The use of cut command is not the ideal solution here, I suppose, but I haven't found anything better at the moment.

As a bonus, it would be cool to find a way to create the subdirs automatically when moving/renaming the files, without having to use mkdir previously (so when I ls to actually rename, I don't have to worry about excluding the newly created directories); but in the end this is a secondary problem.

share|improve this question
add comment

3 Answers

up vote 1 down vote accepted

I guess the solution to your problem is called parameter substitution (have a look at the detailed description). Given a name of a file in filename you'll get its extension using ${filename##*.} and the name w/o extension with ${filename%.*}.

That said, you may want to modify your for loop like:

# for all (*) files in the current directory
for i in *;
do
    # -f checks if it is a file (skip directories)
    [ -f "$i" ] || continue
    # store the file name in filename
    filename=$(basename $i)
    # ext = the extension of the file (here we don't care, jfi)
    ext="${filename##*.}"
    # dir = the file name without its extension
    dir="${filename%.*}"
    # create the directory
    mkdir -p $dir
    # move the file in that directory
    mv $i $dir
done

Unfortunately, I did not get the point of your bonus task, so it'll remain an open challenge... :)

share|improve this answer
    
It worked perfectly, would you care to explain to me what I've exactly done :) ? –  Sekhemty Sep 28 '13 at 11:44
    
I just added some comments to my code. But as I said the trick is called parameter substitution and I recommend to visit the link I've provided in order to understand what's happening :) –  binfalse Sep 28 '13 at 19:26
add comment

Here is an approach with less shell scrips, but more commands.

   find -maxdepth 1 -type f > file-list

Find all files in the current directory and save this list.

   grep -o '\./.*\.' file-list | sed s/\.$// > dir-list

Filter this list to include only file names that have a dot somewhere. Remove this last dot with sed, save this list.

   mkdir $(<dir-list)

Create all the folders.

   sort dir-list > dir-list.s
   sort file-list > file-list.s

This is to allign (by order listed) the file names with the folder names>

   paste file-list dir-list

This will look incorrect because the file file-list will be here. In my case there was also a file called random. Delete those names form the file-list.

   sed -E '/^\.\/(random|file-list)$/d' -i file-list
   sort file-list > file-list.s

Investigate the output of the following command once again: paste file-list dir-list

It should look like this:

   ./CCrtWW].GdH   ./CCrtWW]
   ./c[ifzJlKnEYXO.wXF ./c[ifzJlKnEYXO
   ./FAEhz.u[A ./FAEhz
   ./FGMlrKcJHF.pHp    ./FGMlrKcJHF
   ./HGxKPWZK.MpK  ./HGxKPWZK
   ./JxwNrN.zoj    ./JxwNrN

Once it looks right, make a shell script out the list and sun it:

 paste file-list dir-list | sed 's/^/mv -v /' | sh
share|improve this answer
    
This assumes no spaces or other odd characters in file names. –  Ярослав Рахматуллин Sep 28 '13 at 20:53
add comment

It can also be done with a single command:

find . -maxdepth 1 -type f -exec sh -c ' aux="{}"; aux2="${aux%.*}"; mkdir -p "$aux2"; mv "$aux" "$aux2" ' \; 
share|improve this answer
add comment

Your Answer

 
discard

By posting your answer, you agree to the privacy policy and terms of service.

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