1

I am newbie here, please be patient.

I have a directory containing thousands of files. Filename always start with 1 or 2 letter and have 4 character before underscore "_". The number of files for each pattern can be different, the part of file name after underscore changes.

Sample:

Parentdir:
->AA01_*.pdf 
->AA01_*.html
->AA01_*.txt
...
->ZZ99_*.pdf
->ZZ99_*.html
->ZZ99_*.txt
...
->A001_*.pdf
->A001_*.html
->A001_*.txt
...
->Z999_*.pdf
->Z999_*.html
->Z999_*.txt

I would like to run a command that would create new directories using only letters from file-name and populate them with files starting with these letters. If there is a file with the same name in the destination directory (updated file in the source dir), I'd like to keep most recent one. so:

New dir/files:
->AA
  AA01_*.pdf
  AA01_*.html
  AA01_*.txt
...
->ZZ
  ZZ99_*.pdf
  ZZ99_*.html
  ZZ99_*.txt
...
->A
  A001_*.pdf
  A001_*.html
  A001_*.txt
...
->Z
  Z999_*.pdf
  Z999_*.html
  Z999_*.txt

Can this be accomplished?

Thanks!

1
  • 1
    A1001_*.txt in your example output isn't in your sample input and does not match your stated filename spec. Is it OK to assume you want to match files starting with one-or-two word characters, followed by at least 2 digits and then an underscore? – cas May 19 at 7:44
1

Loop across the set of files. Pick off the alphabetic prefix. Create the directory (if necessary) and move the file into it.

#!/bin/sh
for item in *
do
    if [ -f "$item" ]
    then
        prefix="$(echo "$item" | sed 's/[^A-Z].*//')"
        echo mkdir -p "$prefix"
        echo mv "$item" "$prefix/"
    fi
done

Remove the echo prefixes from mkdir and mv when you're happy it's going to do what you want.

1

Using the perl rename utility (aka prename, file-rename, perl-rename on some linux distributions):

rename  -n  'if (m/^(\w{1,2}?)(\d{2,}_.*)/) {mkdir $1; $_ = "$1/$1$2"}' *

NOTE: this works ONLY with the perl-based rename utility. You can check if you have it installed by running rename -V (or prename -V or file-rename -V or perl-rename -V). If that mentions perl or File::Rename, then it's the perl version of rename. Otherwise, it's probably the rename from util-linux which has completely different and incompatible features and command-line options. This will NOT work with util-linux's rename (or any other version of rename - it requires perl rename)

This uses the first one or two "word" characters (non-greedy match) of each matching filename as the directory, then creates the directory, and renames the file into the directory. A "word" character (\w) is defined in man perlre as:

"Match a "word" character (alphanumeric plus "_", plus other connector punctuation chars plus Unicode marks)"

If this isn't precisely what you want, you can refine the pattern by using, e.g. [[:alpha:]]{1,2}? instead of \w{1,2}? for any alphabetic characters, or [[:upper:]]{1,2} for upper-case only characters.

The \d matches digits only. If you want to allow any word characters before the underscore, use \w. Or use [[:alnum:]] if you want to allow alphabetic and digit characters only.

Filenames that do not match the entire pattern (1-or-2 "word" characters, followed by at least two digits, followed by an underscore) are unaffected.

Note: the -n option makes this a dry-run operation. It will only show what it would do, without actually doing it. Once you're sure that it does what you want, remove the -n (or replace it with -v for verbose output).

e.g. given the following setup script:

#!/bin/bash

for i in A AA Z9 ZZ  ; do
  for j in {01..10} ; do
    touch "$i${j}_.txt"
  done
done

# anomaly
touch A1001_.txt
$ ./setup.sh
$ ls
A01_.txt  A02_.txt  A03_.txt A04_.txt  A05_.txt  A06_.txt  A07_.txt  A08_.txt
A09_.txt A1001_.txt A10_.txt AA01_.txt AA02_.txt AA03_.txt AA04_.txt AA05_.txt
AA06_.txt AA07_.txt AA08_.txt AA09_.txt AA10_.txt setup.sh* Z901_.txt
Z902_.txt Z903_.txt Z904_.txt Z905_.txt Z906_.txt Z907_.txt Z908_.txt
Z909_.txt Z910_.txt ZZ01_.txt ZZ02_.txt ZZ03_.txt ZZ04_.txt ZZ05_.txt
ZZ06_.txt ZZ07_.txt ZZ08_.txt ZZ09_.txt ZZ10_.txt

After running the perl rename script:

$ rename 'if (m/^(\w{1,2}?)(\d{2,}_.*)/) {mkdir $1; $_ = "$1/$1$2"}' *
$ ls */*
A/A01_.txt   A/A02_.txt   A/A03_.txt   A/A04_.txt   A/A05_.txt A/A06_.txt
A/A07_.txt   A/A08_.txt   A/A09_.txt   A/A1001_.txt A/A10_.txt AA/AA01_.txt
AA/AA02_.txt AA/AA03_.txt AA/AA04_.txt AA/AA05_.txt AA/AA06_.txt
AA/AA07_.txt AA/AA08_.txt AA/AA09_.txt AA/AA10_.txt Z/Z901_.txt
Z/Z902_.txt  Z/Z903_.txt  Z/Z904_.txt  Z/Z905_.txt  Z/Z906_.txt  Z/Z907_.txt
Z/Z908_.txt  Z/Z909_.txt  Z/Z910_.txt ZZ/ZZ01_.txt ZZ/ZZ02_.txt ZZ/ZZ03_.txt
ZZ/ZZ04_.txt ZZ/ZZ05_.txt ZZ/ZZ06_.txt ZZ/ZZ07_.txt ZZ/ZZ08_.txt
ZZ/ZZ09_.txt ZZ/ZZ10_.txt
0

Welcome to the forums, Zica!

I'd like to suggest organize - if you have some patience, you can create a script in yaml that can search for files in given locations, rename, move, sort, you name it! I use it myself. I do not know what your proficiency is in Linux, but the Git has a readthedocs which explains everything you need in a very easy to understand way.

1
  • An example would be helpful, to show the OP how to use it to solve their requirement – roaima Jun 16 at 7:00

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.