ksh code explanation

Hi. Can somebody please explain the following lines of KSH code for me? The code checks all sub directories in a specific location which are numbered (E.g. test_01, test_02 ... etc.), then finds the one with highest number and extracts that number from the dir name into the variable num. I'd just like to understand how exactly these cmds work. Thanks.

 
        base=${f##*/} base=${base%_*}
        num=${f##*_}
        (( num > max[$base] )) && max["$base"]=$num

${f##*/} uses Remove matching prefix pattern to expand variable f and remove all characters up to and including the last slash ( / ) character from the expansion. So if f contains /one/two/three/four the expansion will be four

${base%_*} uses Remove matching suffix pattern to expand variable base and remove all characters from the first underscore ( _ ) to the end of the variable. So if base contains june_1234 the expansion will be june

base=${f##*/} base=${base%_*} This will temperately assign base as per first description and then use this to re-assign it as per the 2nd description. so if f=/one/two/three/a_b_c_123 base will become a_b_c and f will remain unchanged.

num=${f##*_} assign variable num to everything following the last underscore from variable f

(( num > max[$base] )) && max["$base"]=$num If interger comparison of num is greater than array max[$base] then assign max[$base] to num.

2 Likes

Excellent answer. Many thanks.

Just a quick follow up.

This line seems only to calculate up to 10. Afterwards num remains 1 ... it doesn't read the second digit. How can I change this code to increment past 10?

num=${f##*_} assign variable num to everything following the last underscore from variable f

Thanks a lot.

This seems to be working OK for me:

for f in  test_1  test_180 test_2 test_10
do
    base=${f##*/} base=${base%_*}
    num=${f##*_}
    (( num > max[base] )) && max[base]=$num
    printf "%d  - %d\n" "$num" "${max[base]}"
done

output:

1  - 1
180  - 180
2  - 180
10  - 180

Could be if you have some leading zero numbers bash is trying to convert them to octal try:

for f in  test_0001  test_0180 test_0002 test_0010
do
    base=${f##*/} base=${base%_*}
    ((num=10#${f##*_}))
    (( num > max[base] )) && max[base]=$num
    printf "%d  - %d\n" "$num" "${max[base]}"
done

Thanks. Actually I should add that I'm trying to create a new directory (incrementing it by 1) every time the code runs. E.g. test_1, test_2, test_3 ...
But when it comes to creating the test_10 the directory it fails as it thinks it already exists .. it just sees test_1 again.

Show us the code that you think should do this. Nothing you have shown us makes any attempt to increment anything by 1.

Hi. Sure. Here it is. Just the same code with mkdir and an increment of the number variable. Works nicely up until the 10th iteration, then it just continuously creates num_10.

BASEDIR=$PWD

mkdir -p ${BASEDIR}
mkdir -p ${BASEDIR}/num_0


for f in ${BASEDIR}/num*; do
        [[ -d $f ]] || continue
        base=${f##*/} base=${base%_*}
        num=${f##*_}

        (( num > max[$base] )) && max["$base"]=$num
done
new_num=$((num+1))

LOGDIR=${BASEDIR}/${base}_${new_num}
mkdir -p ${LOGDIR}

I don't see the purpose of extracting the variable base from $f . Given that you start by creating ${BASEDIR}/num_0 and strip off ${BASEDIR}/ and then strip off _0 (or _ followed by some other string of digits), isn't $base always going to expand to the string num ?

And after you have created the 1st 11 directories in your sequence, the for loop:

for f in ${BASEDIR}/num*

will process those directory names in alphabetic order:

num_0
num_1
num_10
num_2
num_3
num_4
num_5
num_6
num_7
num_8
num_9

and when you exit the loop after processing num_9 , the variable num will be set to 9 .

You go to a lot of trouble in your loop to set the array element max["num"] to 10 , but you don't use that when you create the next directory after you're done. Note that if there are any values for base other than the string num and you tried to use the value of $base after you get out of the loop, there would be a good chance that ${max[$base]} when you get out of the loop would be unrelated to the highest numeric value seen in the loop.

Maybe the following would come closer to what you're trying to do:

BASEDIR=$PWD
max=0

mkdir -p ${BASEDIR}
mkdir -p ${BASEDIR}/num_$max

for f in ${BASEDIR}/num*
do      [[ -d $f ]] || continue
        num=${f##*_}
        (( num > max )) && max=$num
done
new_num=$((max+1))

LOGDIR=${BASEDIR}/num_${new_num}
mkdir -p ${LOGDIR}
1 Like

Yes, that's exactly what I was trying to do. Many many thanks for clarifying that and providing the solution.