5

I have to deal with a large number of files nested inside of directories (one file per directory), resembling something like this:

fred/result.txt
john/result.txt
mary/result.txt
...

I am currently using the following command to process each of those files:

find . -maxdepth 1 -type d \( ! -name . \) -exec bash -c \"cd '{}' && processResult result.txt\" \;

I am looking for something I can add into the end of this command that will change the filenames to fred.txt, etc. and then later move the file into the parent directory to eliminate the extra layer of directories.

What would be the best way to do this?

  • Backslash in filename is not allowed. Your best bet would be to use an underscore or some other special character. It's fairly easy to script , from what i see – Sergiy Kolodyazhnyy Mar 16 '16 at 21:26
  • @Serg Sorry, I meant renaming to 'fred.txt' still within the 'fred' folder - I'll edit the question to make that part clearer. – DTR Mar 16 '16 at 21:29
  • OK. So let me understand this correctly. We have for example /home/user/some_tests/fred/results.txt . You want that to become /home/user/some_tests/fred.txt . Correcto ? – Sergiy Kolodyazhnyy Mar 16 '16 at 21:34
  • @Serg Yes, though at this stage going from /home/user/some_tests/fred/results.txt to /home/user/some_tests/fred/fred.txt would be acceptable – DTR Mar 16 '16 at 21:34
  • OK. ANy preference in scripting language ? Will post later. I am in class atm – Sergiy Kolodyazhnyy Mar 16 '16 at 22:05
2

We can use the classical approach : find with while read combo:

Demo:

$ ls *
fred:
results.txt

jane:
results.txt

john:
results.txt

$> find . -type f -name "results.txt" -printf "/%P\n" | while read FILE ; do DIR=$(dirname "$FILE" );\                            
>  mv ."$FILE" ."$DIR""$DIR".txt;done                                                                                             
$> ls
fred/  jane/  john/
$> ls *
fred:
fred.txt

jane:
jane.txt

john:
john.txt

Use echo before using mv to test the line with your files.

Minor improvement

We can embed the call to bash into exec , with parameter expansion (specifically prefix removal). Basically it's embedding a script into exec call.

$> find "$PWD" -type f -name "results.txt" -exec bash -c ' DIR=$( dirname "{}"  ); echo "{}" "$DIR"/"${DIR##*/}".txt  ' \;                
/home/xieerqi/TESTDIR/fred/results.txt /home/xieerqi/TESTDIR/fred/fred.txt
/home/xieerqi/TESTDIR/jane/results.txt /home/xieerqi/TESTDIR/jane/jane.txt
/home/xieerqi/TESTDIR/john/results.txt /home/xieerqi/TESTDIR/john/john.txt

To make the command general, (working for any file, not just results.txt) just remove -name "results.txt" part. Also see this post for alternative Python solution to this.

4

I think this is legit (using the perl-based rename command)

find dir -name 'results.txt' -exec rename -nv -- 's|/(.*)/(.*)$|/$1/$1.txt|' {} +

e.g. given

$ tree dir
dir
├── fred
│   └── results.txt
├── john
│   └── results.txt
└── mary
    └── results.txt

3 directories, 3 files

then

$ find dir -name 'results.txt' -exec rename -v -- 's|/(.*)/(.*)$|/$1/$1.txt|' {} +
dir/fred/results.txt renamed as dir/fred/fred.txt
dir/john/results.txt renamed as dir/john/john.txt
dir/mary/results.txt renamed as dir/mary/mary.txt

resulting in

$ tree dir
dir
├── fred
│   └── fred.txt
├── john
│   └── john.txt
└── mary
    └── mary.txt

3 directories, 3 files

If you only need to go 1 level below dir, you may be able to simply do

rename -vn -- 's|/(.*)/(.*)$|/$1/$1.txt|' dir/*/results.txt

As always, the -n option is left in for testing purposes

4

The small python script below will do exactly as you describe; it will:

  • rename any file in given directory, named results.txt, according to its superior directory
  • move it one (directory) level up
  • delete the (now empty) directory where the file initially was located

The result:

This:

directory
|
├── fred
│   └── results.txt
├── john
│   └── results.txt
└── mary
    └── results.txt

will become:

directory
|
├── fred.txt
│   
├── john.txt
│ 
└── mary.txt

The script

#!/usr/bin/env python3
import shutil
import os
import sys

dr = sys.argv[1]

for root, dirs, files in os.walk(dr):
    for file in files:
        if file == "results.txt":
            spl = root.split("/"); newname = spl[-1]; sup = ("/").join(spl[:-1])
            shutil.move(root+"/"+file, sup+"/"+newname+".txt"); shutil.rmtree(root)

To use

  • Copy the code below into an empty file, save it as move_rename.py
  • Run it by the command:

    python3 /path/to/move_rename.py <directory_to_reorganize>
    

Note

as always, first test it on a small sample

2

Doing it through find seems overly complicated. Why not use a simple for loop?

for folder in *; do
    (cd "$folder" && processResult result.txt)  # This replaces your previous find(1)
    mv "$folder/result.txt" "${folder}.txt"
    rmdir "$folder"
done
1

If you have multiple files in folders with the same extensions, some of the above commands will overwrite all but one, renaming all files with the same name.

folder1/file1.txt file2.txt --> folder1/folder1.txt

To avoid this, you can use this command or equivalent:

for f in */*.txt ;do fp=$(dirname "$f"); fl=$(basename "$f"); mv "$fp/$fl" "$fp/$fp"_"$fl"; done

By keeping the full filename $fl rather than just the extension, you preserve all the files.

folder1/file1.txt file2.txt --> folder1/folder1_file1.txt folder1_file2.txt

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.