Search code examples
bashloopsif-statementwhile-loop

Using both IF ELSE and CASE within a WHILE loop


The goal is to be able to manually select a file process order, and if the user is not at the computer then let the script select based upon the quantity of files to process.

a) Many files -gt 5 then process smallest files first

b) 5 or less then process the oldest file first

I started simple with just CASE and that is working.

Next I introduced an IF which is also working.

CountMp4=$(ls *.mp4 | wc -l)
while :
do
  read -t50 -p "Select a File Processing Order - [A]lpha [O]ldest [N]ewest [L]argest [S]mallest: "
  if [ $? -gt 0 ]; then
    SelectedFileName=$( ls -rt *.mp4 | head -1 ) && echo -e "No Option Selected\nWill use the Oldest file: $SelectedFileName" && break
  fi
  case $REPLY in
  [aA]*)
    SelectedFileName=$( ls *.mp4 | head -1 ) && echo "Alphabetical: $SelectedFileName" && break
    ;;
  [oO]*)
    SelectedFileName=$( ls -rt *.mp4 | head -1 ) && echo "Oldest: $SelectedFileName" && break
    ;;
  [nN]*)
    SelectedFileName=$( ls -t *.mp4 | head -1 ) && echo "Newest: $SelectedFileName" && break
    ;;
  [lL]*)
    SelectedFileName=$( ls -S *.mp4 | head -1 ) && echo "Largest: $SelectedFileName" && break
    ;;
  [sS]*)
    SelectedFileName=$( ls -rS *.mp4 | head -1 ) && echo "Smallest: $SelectedFileName" && break
    ;;
  *) echo "Please enter A, O, N, L or S"
     ;;
  esac
done

Now I am trying to introduce an ELSE to the IF, but this is where I am stuck.

Perhaps I am going about the solution in the wrong way.

Any guidance is appreciated.

CountMp4=$(ls *.mp4 | wc -l)
while :
do
  read -t50 -p "Select a File Processing Order - [A]lpha [O]ldest [N]ewest [L]argest [S]mallest: "
  if [ $? -gt 0 ] && [ $CountMp4 -gt 5 ]; then
    SelectedFileName=$( ls -rS *.mp4 | head -1 ) && echo -e "No Option Selected\nMany files, using the Smallest file: $SelectedFileName" && break
    else
    SelectedFileName=$( ls -rt *.mp4 | head -1 ) && echo -e "No Option Selected\nWill use the Oldest file: $SelectedFileName" && break
  fi
  case $REPLY in
  [aA]*)
    SelectedFileName=$( ls *.mp4 | head -1 ) && echo "Alphabetical: $SelectedFileName" && break
    ;;
  [oO]*)
    SelectedFileName=$( ls -rt *.mp4 | head -1 ) && echo "Oldest: $SelectedFileName" && break
    ;;
  [nN]*)
    SelectedFileName=$( ls -t *.mp4 | head -1 ) && echo "Newest: $SelectedFileName" && break
    ;;
  [lL]*)
    SelectedFileName=$( ls -S *.mp4 | head -1 ) && echo "Largest: $SelectedFileName" && break
    ;;
  [sS]*)
    SelectedFileName=$( ls -rS *.mp4 | head -1 ) && echo "Smallest: $SelectedFileName" && break
    ;;
  *) echo "Please enter A, O, N, L or S"
     ;;
  esac
done

When I introduce ELSE to the IF, I break the CASE.


Solution

  • Don't create 2 separate ways of getting results, just let the one case statement handle it:

    CountMp4=$(ls *.mp4 | wc -l)
    while :
    do
      read -t50 -p "Select a File Processing Order - [A]lpha [O]ldest [N]ewest [L]argest [S]mallest: "
      if [ $? -gt 0 ]; then
        if [ $CountMp4 -gt 5 ]; then
          echo -e "No Option Selected, many files, use the Smallest file"
          REPLY='S'
        else
          echo -e "No Option Selected, few files, use the Oldest file"
          REPLY='O'
        fi
      fi
      case $REPLY in
      [aA]*)
        SelectedFileName=$( ls *.mp4 | head -1 ) && echo "Alphabetical: $SelectedFileName" && break
        ;;
      [oO]*)
        SelectedFileName=$( ls -rt *.mp4 | head -1 ) && echo "Oldest: $SelectedFileName" && break
        ;;
      [nN]*)
        SelectedFileName=$( ls -t *.mp4 | head -1 ) && echo "Newest: $SelectedFileName" && break
        ;;
      [lL]*)
        SelectedFileName=$( ls -S *.mp4 | head -1 ) && echo "Largest: $SelectedFileName" && break
        ;;
      [sS]*)
        SelectedFileName=$( ls -rS *.mp4 | head -1 ) && echo "Smallest: $SelectedFileName" && break
        ;;
      *) echo "Please enter A, O, N, L or S"
         ;;
      esac
    done
    

    BUT you have several other issues to clean up in your script, start by copy/pasting it into http://shellcheck.net and fixing what that tool tells you about and then consider doing something like this, untested:

    #!/usr/bin/env bash
    
    selectFile() {
        printf '%s\n' "${files[@]}" | sort "$@" | head -1 | cut -f3-
    }
    
    readarray -t files < <(find . -name '*.mp4' -printf '%A@\t%s\t%P\n')
    
    if (( ${#files[@]} > 5 )); then
        dfltReply='S'
    else
        dfltReply='O'
    fi
    
    while :
    do
        if ! read ....; then
            printf 'read failed, using default %s\n' "$dfltReply" >&2
            REPLY="$dfltReply"
        fi
        case $REPLY in
        [aA]*) SelectedFileName=$(selectFile -k3) && break ;;
        [oO]*) SelectedFileName=$(selectFile -nrk1) && break ;;
    ...
        *) printf 'Please enter A, O, N, L or S\n' ;;
        esac
    done
    

    The main part is to use find to find the files, print their latest modification time, size, and name, and store that list in an array, files[]. From then on you just pipe the array contents to sort with appropriate options followed by head and cut to get the file name you need in each case.

    The above is assuming your file names cannot contain newlines - if they can then change -printf '%A@\t%s\t%P\n' to -printf '%A@\t%s\t%P\0', add the -z option to sort, head, and cut (requires GNU tools), change printf '%s\n' "${files[@]}" to printf '%s\0' "${files[@]}" and change the way selectFile() is implemented and called to some variation of:

    selectFile () {
        IFS= read -d '' -r "$1" < <(
            printf '%s\0' "${files[@]}" | sort -z "$2" | head -z -n 1 | cut -z -f3-
        )
    }
    selectFile SelectedFileName -k3
    

    because command substitution var=$(func) doesn't support NUL bytes in the assignment.