Script to mv set of directory recursively

:frowning: i,

OS version is below: uname -a
SunOS [hostname] 5.8 Generic_Virtual sun4v sparc sun4v

bash --version
GNU bash, version 2.03.0(1)-release (sparc-sun-solaris)
Copyright 1998 Free Software Foundation, Inc.

Unfortunately, I can't upgrade, I am NOT the sysadmin, just a user of sort.

I have a list of directories that I want to mv every so often.
Directories are from dir0 to dir15.
So, dir15 is the last/max directory
We only want to mv directories dir4 to dir15.
Ideally dir4 to dir15 are sequential with no gap, i.e. dir4,dir5,dir6 until dir15
However, there can be instance where a gap may exist, this is alright so long as we can mv them as should be. I don't know why there's a gap but if there is then the next step will be to create blank directory of the missing directory.

I am uploading 4 text files that are sample listing from running

ls -1d dir* | egrep -v "^dir0$|^dir1$|^dir2$|^dir3$" | sort -k1.4nr

Basically, what I am wanting to achieve is to keep a set of directory from dir4 to dir15 and mv them every so often like below for example

mv dir10 dir11
mv dir9 dir10
mv dir8 dir9
mv dir7 dir8
mv dir6 dir7
...
mv dir4 dir5
recreate dir4 based on directory structure of dir3 without the files? how? :(

Below is what is my script looks so far. It is a bit of a mess but seems to work fine to what I am wanting to do but will have to do more testing before actually doing the mv, at the moment, testing with echo commands.

The script is processing each sample file that has different scenario for testing.

I am posting here in case, there is a simpler way of doing this sort of. I am not sure if I can simply do alternating rows for the mv's.

#!/bin/bash
#
max=15
min=4
switch=0
echo "## USING sample1.txt"
cat sample1.txt | cut -c4- | awk '$1 <= 15 { print "dir"$1 }' > sample1.txt.tmp
cp sample1.txt.tmp sample1.txt
cat sample1.txt
echo
while read -r line
do
  vcurrent=$( echo $line | cut -c4- )
  if [[ ${vcurrent} -gt ${max} ]] ; then ## this is for sanity in case the filter above fails for some reason
    continue
  fi
  if [[ ${switch} -eq 0 ]] ; then
    vnext=$(( vcurrent + 1 ))
    if [[ ${vnext} -le ${max} ]] ; then
      echo "mv $line dir${vnext}"
    else
      echo "rm -r $vcurrent"
    fi
    switch=1
    vprev=${line}
  else
    echo "mv ${line} ${vprev}"
    vprev=${line}
  fi
done < sample1.txt
if [[ ! -d "dir4" ]] ; then
  echo "mkdir dir4"
fi
echo
echo "## DONE processing sample1.txt"
echo "##############################################"
echo
###########################################################################

max=15
min=4
switch=0
echo "## USING sample2.txt"
cat sample2.txt | cut -c4- | awk '$1 <= 15 { print "dir"$1 }' > sample2.txt.tmp
cp sample2.txt.tmp sample2.txt
cat sample2.txt
echo
while read -r line
do
  vcurrent=$( echo $line | cut -c4- )
  if [[ ${vcurrent} -gt ${max} ]] ; then ## this is for sanity in case the filter above fails for some reason
    continue
  fi
  if [[ ${switch} -eq 0 ]] ; then
    vnext=$(( vcurrent + 1 ))
    if [[ ${vnext} -le ${max} ]] ; then
      echo "mv $line dir${vnext}"
    else
      echo "rm -r $vcurrent"
    fi
    switch=1
    vprev=${line}
  else
    echo "mv ${line} ${vprev}"
    vprev=${line}
  fi
done < sample2.txt
if [[ ! -d "dir4" ]] ; then
  echo "mkdir dir4 OR cp -rp dir3 dir4 OR mkdir dir4 that has a similar dir structure as dir3 but no files"
fi
echo
echo "## DONE processing sample2.txt"
echo "##############################################"
echo
###########################################################################

max=15
min=4
switch=0
echo "## USING sample3.txt"
cat sample3.txt | cut -c4- | awk '$1 <= 15 { print "dir"$1 }' > sample3.txt.tmp
cp sample3.txt.tmp sample3.txt
cat sample3.txt
echo
while read -r line
do
  vcurrent=$( echo $line | cut -c4- )
  if [[ ${vcurrent} -gt ${max} ]] ; then ## this is for sanity in case the filter above fails for some reason
    continue
  fi
  if [[ ${switch} -eq 0 ]] ; then
    vnext=$(( vcurrent + 1 ))
    if [[ ${vnext} -le ${max} ]] ; then
      echo "mv $line dir${vnext}"
    else
      echo "rm -r $vcurrent"
    fi
    switch=1
    vprev=${line}
  else
    echo "mv ${line} ${vprev}"
    vprev=${line}
  fi
done < sample3.txt
if [[ ! -d "dir4" ]] ; then
  echo "mkdir dir4 OR cp -rp dir3 dir4 OR mkdir dir4 that has a similar dir structure as dir3 but no files"
fi
echo
echo "## DONE processing sample3.txt"
echo "##############################################"
echo
###########################################################################

max=15
min=4
switch=0
echo "## USING sample4.txt"
cat sample4.txt | cut -c4- | awk '$1 <= 15 { print "dir"$1 }' > sample4.txt.tmp
cp sample4.txt.tmp sample4.txt
cat sample4.txt
echo
while read -r line
do
  vcurrent=$( echo $line | cut -c4- )
  if [[ ${vcurrent} -gt ${max} ]] ; then ## this is for sanity in case the filter above fails for some reason
    continue
  fi
  if [[ ${switch} -eq 0 ]] ; then
    vnext=$(( vcurrent + 1 ))
    if [[ ${vnext} -le ${max} ]] ; then
      echo "mv $line dir${vnext}"
    else
      echo "rm -r $vcurrent"
    fi
    switch=1
    vprev=${line}
  else
    echo "mv ${line} ${vprev}"
    vprev=${line}
  fi
done < sample4.txt
if [[ ! -d "dir4" ]] ; then
  echo "mkdir dir4 OR cp -rp dir3 dir4 OR mkdir dir4 that has a similar dir structure as dir3 but no files"
fi
echo
echo "## DONE processing sample4.txt"
echo "##############################################"
echo
###########################################################################

Sample runtime output below:

$: ./dir_mv.bash
## USING sample1.txt
dir14
dir9
dir8
dir7
dir5
dir4

mv dir14 dir15
mv dir9 dir14
mv dir8 dir9
mv dir7 dir8
mv dir5 dir7
mv dir4 dir5
mkdir dir4

## DONE processing sample1.txt
##############################################

## USING sample2.txt
dir10
dir9
dir8
dir7
dir5

mv dir10 dir11
mv dir9 dir10
mv dir8 dir9
mv dir7 dir8
mv dir5 dir7
mkdir dir4 OR cp -rp dir3 dir4 OR mkdir dir4 that has a similar dir structure as dir3 but no files

## DONE processing sample2.txt
##############################################

## USING sample3.txt
dir15
dir9
dir8
dir7
dir5

rm -r 15
mv dir9 dir15
mv dir8 dir9
mv dir7 dir8
mv dir5 dir7
mkdir dir4 OR cp -rp dir3 dir4 OR mkdir dir4 that has a similar dir structure as dir3 but no files

## DONE processing sample3.txt
##############################################

## USING sample4.txt
dir15
dir9
dir8
dir7
dir5

rm -r 15
mv dir9 dir15
mv dir8 dir9
mv dir7 dir8
mv dir5 dir7
mkdir dir4 OR cp -rp dir3 dir4 OR mkdir dir4 that has a similar dir structure as dir3 but no files

## DONE processing sample4.txt
##############################################

After doing the mv, I will want to create missing directories between dir4 to dir15. I have not gone thru that part yet.

Note that I am doing the one below to limit the max directory to dir15 only.

cat sample1.txt | cut -c4- | awk '$1 <= 15 { print "dir"$1 }' > sample1.txt.tmp
cp sample1.txt.tmp sample1.txt
cat sample1.txt

sed -n '/dir15/,$p' sample4.txt appears to work too because sample4.txt contains dir15 BUT if dir15 is not a pattern in the file, it prints nothing. So, if I do sed -n '/dir15/,$p' sample1.txt, it prints nothing

dir_mv.sh (3.8 KB)
sample1.txt (31 Bytes)
sample2.txt (26 Bytes)
sample3.txt (26 Bytes)
sample4.txt (44 Bytes)

At a first glance,

should certainly be
echo rm -r "dir$vcurrent"
or, less safe,
echo rm -r "$line"

And put each $var or ${var} in quotes.

Good spotting there, thanks. Good thing, am still just doing echo atm :slight_smile:

cat is not needed here, and you can even split within awk (and do an implicit { print } action)

awk 'substr($0, 4)+0 <= 15' sample1.txt > sample1.txt.tmp &&
cp sample1.txt.tmp sample1.txt

But awk is tricky; I must add +0 to enforce a numeric comparison, otherwise it does a string comparison.

This is at risk.? If the destination dir exists, the source dir will be moved inside.
Safe is
[[ ! -e "dir${vnext}" ]] && echo mv "$line" "dir${vnext}"
Or consider this a fatal error:

if [[ -e  "dir${vnext}" ]] ; then "Fatal error: dir${vnext} must not exist"; exit 1; fi
echo mv "$line" "dir${vnext}"

A problem here is that only the real mv will remove the source dir.

I would go a completely different way. Since your set of directory names is constant, there's no need to list directories or grab them from an external or temporary file. My suggestion:

  1. Create the directory name suffix sequence with seq command, from 14 to 4 (excuse me if seq isn't available in SunOS)
  2. Use the sequence to create a for or while read loop
  3. Test if each origin directory exists
  4. If it does, move origin to destiny
  5. If it doesn't, create destiny
  6. Create dir4

Pls come back showing where you got and eventual difficulties.

1 Like

How does this work for you, @newbie_01

#!/bin/bash

# Define the min and max directory limits
max=15
min=4

# Process each input file passed as an argument
for file in sample1.txt sample2.txt sample3.txt sample4.txt; do
    echo "## USING $file"

    # Filter and sort directories from the file, limiting to max=15
    dirs=$(cut -c4- "$file" | awk -v max="$max" '$1 <= max' | sort -nr)

    # Initialize variables
    last_dir=$max

    # Move directories in reverse order
    for dir_num in $dirs; do
        if [[ $dir_num -lt $min ]]; then
            continue
        fi

        if [[ $dir_num -eq $last_dir ]]; then
            echo "rm -r dir$last_dir"
        else
            echo "mv dir$dir_num dir$((dir_num + 1))"
        fi
        last_dir=$dir_num
    done

    # Create missing directories from min to max
    for ((i=min; i<=max; i++)); do
        if [[ ! -d "dir$i" ]]; then
            if [[ $i -eq $min ]]; then
                echo "mkdir dir$i based on dir3 structure (excluding files)"
            else
                echo "mkdir dir$i"
            fi
        fi
    done

    echo
    echo "## DONE processing $file"
    echo "##############################################"
    echo
done

Kept your echo print statements for debugging....

AFAIR bash-2 does not know postfix/prefix increment, so i++ must become i+=1
Furthermore, this always increments dir$((dir_num + 1)) , while the o/p mostly takes the previous dir$last_dir

The following does it like the o/p:

#!/bin/bash

dirbase="dir"
max=15
min=4

# Loop over these sample files
#for file in sample{1..4}.txt
# Loop over existing sample files
for file in sample[1-9].txt
do
  switch=0
  echo "## USING $file"
  cat "$file"
  echo

  while read -r line
  do
    vcurrent=${line#$dirbase}
    case "$vcurrent" in ( "" | *[!0-9]* | "$line" )
      continue
    esac
    if [[ $vcurrent -gt $max ]] || [[ $vcurrent -lt $min ]] ; then
      continue
    fi
# Increment or delete the first given dir
    if [[ $switch -eq 0 ]] ; then
      if [[ $vcurrent -ne $max ]] ; then
        echo mv "$line" "$dirbase$(( vcurrent + 1 ))"
      else
        echo rm -r "$dirbase$vcurrent"
      fi
      switch=1
    else
      echo mv "$line" "$vprev"
    fi
    vprev=$line
  done < "$file" #line

# Recreate the last given dir
  if [[ $switch -ne 0 ]] ; then
    echo mkdir "$vprev"
  fi
  echo
  echo "## DONE processing $file"
  echo "##############################################"
  echo
done #file
1 Like

@EmersonPrado, interesting idea. Then the sample files are not needed at all.
The following is an implementation attempt:

#!/bin/bash

dirbase="dir"
max=15
min=4

switch=0
for (( di=max; di >= min; di-=1 ))
do
# Source dir must exist
  sdir="$dirbase$di"
  if [[ ! -d "$sdir" ]] ; then
    continue
  fi
# Increment or delete the first (highest) dir
  if [[ $switch -eq 0 ]] ; then
    if [[ $di -ne $max ]] ; then
      echo mv "$sdir" "$dirbase$(( di + 1 ))"
    else
      echo rm -r "$sdir"
    fi
    switch=1
  else
    echo mv "$sdir" "$pdir"
  fi
  pdir=$sdir
done #di

# Recreate the last (lowest) dir
if [[ $switch -ne 0 ]] ; then
  echo mkdir "$pdir"
fi
1 Like

I was hoping the OP, @newbie_01 , would give it a try but, since you took that step, just two comments:

  1. When you move dir_13 to dir_14, the original dir_14 disappears, so all the stuff related to switch is not needed either.

    mkdir dir_13 dir_14
    ls -1di dir_*
    24393723 dir_13
    24393724 dir_14
    
    mv -T dir_13 dir_14
    ls -1di dir_*
    24393723 dir_14 # Inode of original dir_13 - Original dir_14 doesn't exist anymore
    
  2. If I understood the challenge correctly, if the origin directory doesn't exist, there's still an action - create an empty destiny directory. So I would avoid the continue and, instead, choose to move or (re)create destiny whether origin exists or not.

Yeah, had thought about this. And indeed, the source dir did get moved inside. And I've done a similar thing of doing a -e test and then mv the existing directory to dir${vnext}_EXIST, then do the mv. Then I do a listing of _EXIST to check later on.

Yeah seq is not available in SunOS

Hi,

Thanks for all your suggestions.

I will test all provided solutions. Won't be able to use the seq solution as it is not available on this Solaris 5.8 :frowning: -- Yeah am not playing tricks on your vision, this is Solaris8.

To make things simpler, I'll just do all the mv's first and then after that is done.

I'll do another loop from dir4 to dir[current_max] and create the missing directory in the sequence, albeit blank directory.

It's a pity I am on this version of bash and I can't use some of the for loop'ing.

$: bash --version
GNU bash, version 2.03.0(3)-release (sparc-sun-solaris)
Copyright 1998 Free Software Foundation, Inc.

Unfortunately, this gives error due to the bash version that I am on. This is similar to the error running MadeInGermany's one.

$: ./neo.bash
./neo.bash: line 32: syntax error near unexpected token `(('
./neo.bash: line 32: `    for ((i=min; i<=max; i++)); do'

$: bash --version
GNU bash, version 2.03.0(3)-release (sparc-sun-solaris)
Copyright 1998 Free Software Foundation, Inc.

Hi,

I can't use the one that uses this for loop due to the old bash version :frowning:

$: ./mig.bash
./mig.bash: line 8: syntax error near unexpected token `(('
./mig.bash: line 8: `for (( di=max; di >= min; di-=1 ))'

$: bash --version
GNU bash, version 2.03.0(3)-release (sparc-sun-solaris)
Copyright 1998 Free Software Foundation, Inc.

So, I am sticking with the one that doesn't use the for loop above.

Ah okay. You can convert the for loop to a while loop.

for (( di=max; di >= min; di-=1 ))
do
  ...
done

becomes

di=$(( max + 1 ))
while
  (( di-=1 >= min ))
do
  ...
done

And, if even the plain (( )) fails:

di=$(( max + 1 ))
while
  di=$(( di - 1 ))
  [[ $di -ge $min ]]
do
  ...
done

If all else fails in creating a sequence using Bash resources, do your system have bc?

number=13
while [ $number != 3 ] ; do
  echo dir_$number
  number=`echo $number - 1 | bc`
done
dir_13
dir_12
dir_11
dir_10
dir_9
dir_8
dir_7
dir_6
dir_5
dir_4

My humble opinion is that to do both things - moving existing directories and creating missing ones - in the same loop is simpler than in two loops. The main reason is that, in the first one, you have to test for directory existence anyway. So, why not already create it if the test results false?

The OP didn’t specify any requirement to use a single loop or reduce code length. They haven’t indicated that simplicity or fewer lines are a priority.

Our goal is to help users solve their problems based on their stated needs, rather than suggesting code refinements that may not align with their requirements.