0

i'm trying to have a script organize files by their first few letters into directories made automatically. my problem is i'd like to skip making directories for single files

i have found a command on here that very simply does what i want. if i run it i will have my files placed into alphabetical folders, A, B, etc. after playing with that, i studied the unix "cut " command and have changed the code into a .sh script. now it works and it will make the folders the first four letters of the filename(i want a folder for each customer, and this way i can easily rename them later).

i am afraid to run it. i do not want to end up with a bunch of odd folders that only contain one file each. it also seems to easy. i like to learn and i have my linux shell scripting bible handy.

how can i make my script sort files into directories as it does now, but have it not make directories that would only contain a single file?

like if i had the following seven files:

robert01.cdr
robert02.cdr
mom01.cdr
mom02.cdr
mom03.cdr
father.cdr
sister.cdr 

i would want the roberts and moms in their respective directories, but i would want the single file father and the single file sister to remain unfoldered, ie:

[robe]
[mom0]
father.cdr
sister.cdr

i'm new, this is my first time using this site. i'm not lazy looking for someone to do this for me, i'm just a little lost. if someone could provide a link to a sample i could learn from, or at least point me towards a command i might study up on, that is what i want. i'm here to learn (:

we have a small sign business with a few thousand unorganized coreldraw files in about 50 different folders on a few different computers. i'm trying to undo years of bad organizing while learning in the process

for R in *.cdr; do
    name=`echo "$R"|sed 's/ -.*//'`
    letter=`echo "$name"|cut -c1-4`
    dir="rawr/$letter/$name"
    mkdir -p "$dir"
    mv "$R" "$dir"
done
  • 1
    Using bash, you can get the first 4 characters with a simple built-in parameter expansion, e.g. `${R:0:4}`. You can then use a simple glob and `wc` to determine whether to create the directory, e.g. `[ $(ls -l "${R:0:4}"* | wc -l) -gt 1 ] && mkdir -p "${R:0:4}" && mv "$R" "${R:0:4}"` (much more efficient that piping `echo` to `sed` and `echo` to `cut`) – David C. Rankin Dec 27 '18 at 06:34
  • @DavidCRankin Though [don't use `ls` in scripts](http://mywiki.wooledge.org/ParsingLs). – tripleee Dec 27 '18 at 06:38
  • Agreed, but it is somewhat innocuous here since you are not using the content of the name itself from `ls` and the glob, but rather whether or not a file exists with that prefix to generate a line of output. (meaning there is no issue with whitespace, and the only corner-case would be names with embedded `'\n'` (or the like) as part of the name) – David C. Rankin Dec 27 '18 at 06:47
  • @tripleee perhaps a better method to eliminate `ls` altogether is to simply use an array based on the glob and test the number of elements, e.g. `IFS=$'\n'; a=( "${R:0:4}"* ); [ ${#a[@]} -gt 1 ] && mkdir -p "${R:0:4}" && mv "$R" "${R:0:4}"` – David C. Rankin Dec 27 '18 at 06:51
  • I added a new answer to https://stackoverflow.com/questions/21143043/find-count-of-files-matching-a-pattern-in-a-directory-in-linux – tripleee Dec 27 '18 at 06:53
  • Yep, that works too. – David C. Rankin Dec 27 '18 at 06:55

1 Answers1

1

Continuing from the comments, and with the new nugget posted by @tripleee, you can greatly increase the efficiency of your script simply by using the bash built-in parameter expansion for string indexes to return the first 4-characters of each filename, and then using the simple helper function to count the number of occurrences of filenames matching the first 4-characters used as a glob, e.g.

#!/bin/bash

count_args() {
    echo $#
}

for R in *.cdr; do
    if [ $(count_args "${R:0:4}"*) -gt 1 ]; then
        mkdir -p "${R:0:4}" && mv "$R" "${R:0:4}"
    fi
done

This completely eliminates piping the output of echo to sed and then piping the output of echo to cut (which you want to avoid calling external utilities within a loop to the greatest extent possible to prevent spawning multiple subshells every iteration)

In this case, using your input filenames, and the script above, you could tidy up the directory as you intend, e.g.

Example Before Directory

$ ls -l
total 0
-rw-r--r-- 1 david david   0 Dec 27 00:53 father.cdr
-rw-r--r-- 1 david david   0 Dec 27 00:53 mom01.cdr
-rw-r--r-- 1 david david   0 Dec 27 00:53 mom02.cdr
-rw-r--r-- 1 david david   0 Dec 27 00:53 mom03.cdr
-rw-r--r-- 1 david david   0 Dec 27 00:53 robert01.cdr
-rw-r--r-- 1 david david   0 Dec 27 00:53 robert02.cdr
-rw-r--r-- 1 david david   0 Dec 27 00:53 sister.cdr

Example Use/After Directory

$ ../moveprefix.sh

$ ls -l
total 0
drwxr-xr-x 2 david david 100 Dec 27 01:09 mom0
drwxr-xr-x 2 david david  80 Dec 27 01:09 robe
-rw-r--r-- 1 david david   0 Dec 27 00:53 father.cdr
-rw-r--r-- 1 david david   0 Dec 27 00:53 sister.cdr

$ ls -al mom0
-rw-r--r-- 1 david david 0 Dec 27 00:53 mom01.cdr
-rw-r--r-- 1 david david 0 Dec 27 00:53 mom02.cdr
-rw-r--r-- 1 david david 0 Dec 27 00:53 mom03.cdr

(note: the script was placed one level above the current directory to avoid the script filename. There is nothing that prevents defining the count_args function in the current shell, and then putting this altogether as a one-liner without using a separate script file)

Look things over and let me know if you have further questions.

tripleee
  • 175,061
  • 34
  • 275
  • 318
David C. Rankin
  • 81,885
  • 6
  • 58
  • 85