0

I'm trying to write a bash script that will create a new directory with a name based on the last directory created. For example - if I have the following folders in a directory:

00001 00002 00003

and I run the script again I would like it to determine that the last folder is '00003' and then create '00004'.

The following command seems to get the last folder name from the 'output' directory but I don't seem to be able to manipulate the value in the 'var' variable as it is a directory and not a string or integer.

var=$(ls -t output | grep -v /$ | head -1)

Any help would be much appreciated.

Paul W
  • 3
  • 1
  • See [BashFAQ #3](https://mywiki.wooledge.org/BashFAQ/003) and [Why you shouldn't parse the output of `ls`](https://mywiki.wooledge.org/ParsingLs) – Charles Duffy Nov 29 '22 at 00:23
  • ...that said, bash doesn't really have an idea of data types. "is a directory and not a string or integer" -- there's no directory type in bash, and there's only _barely_ an integer type. Your `var` contents are 100% purely a string, but when you set `foo=3`, `foo` is also a string (but works perfectly well with standard math operators); that's normal is POSIX-y shell languages. – Charles Duffy Nov 29 '22 at 00:24
  • What _is_ a complicating factor here is the presence of leading `0`s, which make your values be treated as octal rather than decimal by default, but we have existing Q&A telling you how to fix that. And the presence of the `/`, of course, but that's trivial to strip with parameter expansion. – Charles Duffy Nov 29 '22 at 00:25
  • ...a more important thing to think about is race conditions; what happens if two copies of your script run at once? If they both run `ls` at the same time, they can both think the last folder is 00003 and thus both think the next folder should be 00004. – Charles Duffy Nov 29 '22 at 00:26
  • anyhow -- have you thought of maintaining a symlink pointing to the latest directory? That way it's O(1) to read; in addition to the problems described in the link above, `ls -t` has to read the whole directory so it gets slower the larger that directory is; whereas `readlink` is a constant-time operation. Doesn't fix the concurrency issues, but running `mkdir` without `-p` and checking the exit status (and then looking, on failure, for whether the named directory already exists -- so you know you need to go on to the next one) will let you catch when there's a race going on. – Charles Duffy Nov 29 '22 at 00:28
  • Anyhow, some relevant existing Q&A: [Incrementing a number in bash with leading 0](https://stackoverflow.com/questions/16737198/incrementing-a-number-in-bash-with-leading-0); [remove a fixed suffix from a string in bash](https://stackoverflow.com/questions/16623835/remove-a-fixed-prefix-suffix-from-a-string-in-bash) – Charles Duffy Nov 29 '22 at 00:34
  • ...and to avoid hackery with `ls`, see KamilCuk's answer on [get the newest directory to a variable in bash](https://stackoverflow.com/a/64466737/14122) – Charles Duffy Nov 29 '22 at 00:35
  • Either keep a counter, or start from 1 each time looping with `printf -v dname "%05d" $counter`. Check if the directory exists `[ -d "$dname" ] && continue` and when you reach the first `$dname` that doesn't exist, `mkdir -p "$dname"` and `break` the loop. – David C. Rankin Nov 29 '22 at 01:30

2 Answers2

0

Continuing from my comment, the simplest way to create sequential 5-digit directory names, creating the next available directory on each run of your script is to form the number using printf -v var ... Example, to create a 5-digit number storing the result in var you use printf -v var "%05d" "$count". If count contained 4 that would store 00004 in var.

To determine which is the next available directory, loop starting at 1 with while [ -d "parent/$var" ] and increment the number in var each time until you find a number in var for which the parent/$var directory doesn't exist and then simply create the new directory. You can set a default value for parent of . (present working directory) or take the parent path as your first argument. (you can take the number to start counting at as your 2nd argument with a default of 1)

Putting it altogether, you could do:

#!/bin/bash

parent="${1:-.}"    ## parent directory as 1st arg (default .)
count="${2:-1}"     ## initial count as 2nd arg    (default 1)

printf -v dname "%05d" "$count"           ## store 5 digit number in dname

while [ -d "$parent/$dname" ]             ## while dir exists
do
  ((count++))                             ## increment count
  printf -v dname "%05d" "$count"         ## store new 5 digit number in dname
done

printf "creating %s\n" "$parent/$dname"   ## (optional) output dirname
mkdir -p "$parent/$dname"                 ## create dir

Example Use/Output

With the script in createseqdir.sh and the parent directory location as dat, e.g.

$ l dat
total 24
drwxr-xr-x 5 david david 4096 Nov 28 20:16 .
drwxr-xr-x 3 david david 4096 Nov 28 20:16 ..
drwxr-xr-x 2 david david 4096 Nov 28 20:12 00001
drwxr-xr-x 2 david david 4096 Nov 28 20:12 00002
drwxr-xr-x 2 david david 4096 Nov 28 20:12 00003

You could do:

$ bash createseqdir.sh dat
creating dat/00004

Resulting in:

$ l dat
total 28
drwxr-xr-x 6 david david 4096 Nov 28 20:30 .
drwxr-xr-x 3 david david 4096 Nov 28 20:29 ..
drwxr-xr-x 2 david david 4096 Nov 28 20:12 00001
drwxr-xr-x 2 david david 4096 Nov 28 20:12 00002
drwxr-xr-x 2 david david 4096 Nov 28 20:12 00003
drwxr-xr-x 2 david david 4096 Nov 28 20:30 00004
David C. Rankin
  • 81,885
  • 6
  • 58
  • 85
0

An alternative method using an array and negative array index -1 (which points to the last element of the array) would be:

#!/bin/bash

dirs=([0-9][0-9][0-9][0-9][0-9])
mkdir $(printf '%05d' $((1 + 10#${dirs[-1]}))) || exit

This assumes at least one directory with the pattern [0-9][0-9][0-9][0-9][0-9] exists. The 10# is required because a bare digit sequence starting with 0 is interpreted in octal base.

M. Nejat Aydin
  • 9,597
  • 1
  • 7
  • 17