0

I am pretty green when it comes to bash scripts and completely new to command line functionality in bash. I tried my hand at a script which is supposed to be useable both with command line arguments as well as manual setting of variable values, if the user prefers to edit the code directly.

The general idea is this:

  • Define command line arguments for different functions using the while/case/getopt structure.
  • Set the value of the variables of each option inside the respective case.
  • Later, check if the command line argument was actually provided using an if case.
  • if not, set the value to a default parameter there.

This way one can either use myscript.sh -i somestring, or just set the variable associated with -i inside the if case manually. This way the script can also be run just by doing ./myscript.sh.

I have figured out that the if cases I have in my code don't actually do anything, or at least not what I want them to. When I run the script without commandline arguments and then echo out the default values that should be set by the if cases, they are empty.

This means that the for loop later on in the script cannot work, since these variables haven't been set to anything. The line that the script gets stuck on is slurm_file_string=$(cat $slurm_template_file | sed "s/INPUT_FILE/$gs_file/")

So idk how to implement what I'm trying to achieve and how to fix this "getting stuck issue". I need to change the if cases somehow so that the default values inside the if cases actually do something, but idk how.

Here's my code:

#!/bin/bash

# TODO: 
    # Need to figure out if I can launch all slurm jobs with "&" and let slurm handle the rest.
    # Add scratch removal logic to slurm file template. Makes more sense to do it per run in the file that actually runs gaussian.
    # Add commandline options for:
        # input folder (-i) 
        # verbose mode (-v)
        # extension name (-x)
        # slurm template name (-t)
    # Define function be_verbose() which wraps the logic for printing additional info if -v is set to true.

# INFO: Script for running a batch of gaussian jobs using the SLURM scheduler.
    # General program flow: 
        # The script finds all gaussian input files in inps_folder (needs to be manually specified)
            # The input files are identified by the file extension in "extension"
        # The script iterates over all gaussian input files.
            # For each file a slurm file is created based on the template: submit_gaussian_template.slurm
            # Inside the template the string "INPUT_FILE" is replaced by the current input file.
            # The new slurm file is copied to the input files folder
            # The script changes directories to the common pwd of the slurm file and gaussian input file
            # The slurm file is executed and then the next iteration starts.
                # The cleanup is handeled by the shell code inside the slurm file (deleting temp files from /scratch)
            
# IMPORTANT:
    # This script is designed for a certain folder structure.
    # It is required that all your gaussian input files are located in one directory.
    # That folder must be in the same dir as this script.
    # There must be a template slurm file in the directory of this script
    # There must be a string called INPUT_FILE inside the template slurm file 
################################################################################################################################################



# this implements command line functionality. If you do not wish to use them, look for MANUAL_VARS_SPEC below to specify your variables that way.
while getopts "i:vx:t:" opt; do
    case $opt in
        i)
            inps_folder="$OPTARG"
            ;;
            v)
            verbose=true
        ;;
        x)
        extension="$OPTARG"
        echo "$extension"
        ;;
        t)
        slurm_template_file="$OPTARG"
        ;;
        \?)
        echo "Usage: $0 [-i input_folder] [-s value_s] [-k value_k]"
        exit 1
        ;;
    esac
done


# MANUAL_VARS_SPEC: Change the varibles to the appropriate values if you do not wish to use the comman line options.
# These are essentially the default settings of the script.

# folder for input files
if [ -n "$inps_folder" ]; then # "if the commandline option is an empty string (not set), set the variable to this value."
    inps_folder="testinps"
fi

# verbose mode
if [ -n "$verbose" ]; then
    verbose=0
fi

# file extension of your gaussian input files
if [ -n "$extension" ]; then
    echo "AFASGSG"
    extension="gin"
fi

# slurm template file name
if [ -n "$slurm_template_file" ]; then
    slurm_template_file="submit_gaussian_template.slurm"
fi




# HELPER FUNCTIONS
function be_verbose(){
    local print_string="$1" # set the first argument provided to the funtion to the local var "print_string".
    if [ $verbose = true ]; then
        echo "$print_string"
    fi
}

echo "$inps_folder"
echo "$verbose"
echo "$extension"
echo "$slurm_template_file"


#### START OF MAIN LOOP.
files="${inps_folder}/*.${extension}" # iteratable for all gaussian input files.

for file in $files; do
    gs_file=$(basename $file) # get the file without the preceeding path
    gs_file_name="${gs_file%.*}" # get the file name without the extension
    
    # Make a new slurm file for the current job based on the template slurm file in the pwd.
    slurm_file_string=$(cat $slurm_template_file | sed "s/INPUT_FILE/$gs_file/") # get template and replace INPUT_FILE with gaussian input file. FAIL!!!!
    slurm_file="${gs_file_name}.slurm"
    echo "$slurm_file_string" > "$slurm_file" # write the string of the new slurm file to a new file
    mv "$slurm_file" "${inps_folder}/${slurm_file}" # move the new slurm file to the inps_folder

    cd "$inps_folder" # change directories so that slurm files can be executed
    echo "Is running ${gs_file}" #PUT HERE WHATEVER THE COMMAND FOR RUNNIGN A SLURM FILE IS &
    cd ..
done

Thanks in advance for any input.

1 Answer 1

5

Generally it's easier to help if there's a "minimum verifiable example" of code - something just sufficient to demonstrate the problem. However, here I think I can see the problem.

When parsing options you have code like this:

while getopts 't:' opt
do
    case "$opt" in
        (t) slurm_template_file="$OPTARG" ;;
    esac
done

But later on you have this, which says "if the variable has a value, set it to a fixed value":

if [ -n "$slurm_template_file" ]
then
    slurm_template_file='submit_gaussian_template.slurm'
fi

I would have expected this code to use -z instead of -n so that if the variable was unset or empty you assigned a default value.

My approach would be to set variables to have their default values and then override them with command-line switches. If necessary for later decisions I would also set a flag to indicate that the user set the value explicitly, but in my experience this isn't often needed.

slurm_template_file='submit_gaussian_template.slurm'
slurm_template_file_isset=false

while getopts 't:' opt
do
    case "$opt" in
        (t)
            slurm_template_file="$OPTARG"
            slurm_template_file_isset=true
            ;;
    esac
done

if "$slurm_template_file_isset"
then
    echo "User overrode the default" >&2
fi
echo "The working value is: $slurm_template_file" >&2

Alternatively you can set default values at the top of the code and use them whenever the working value is unset. With long variable names the code is a little more unwieldy but with care it can be clearer:

template_default='submit_gaussian_template.slurm'
template=

while getopts 't:' opt
do
    case "$opt" in
        (t)
            template="$OPTARG"
            ;;
    esac
done

if [ -n "$template" ]
then
    echo "User overrode the default: $template" >&2
fi
echo "The working value is: ${template:-$template_default}" >&2
4
  • 2
    Yep. setting variables to default values and then allowing command-line options to override the defaults is the standard/correct/best way to do it. I don't really see any point in mentioning other methods, except to say "you could do that, but it's far more work and definitely not worth the bother...also, more code = more chances to introduce bugs - and that's especially true when you have two or more sections of code doing basically the same thing".
    – cas
    Aug 2, 2023 at 13:45
  • @cas I've needed to identify when the user has set a value, even if that value is the same as the default. Not often, mind Aug 2, 2023 at 15:00
  • 1
    same here. and yes, not often. very rarely. easily handled in the main while getopts loop case statement, just set another variable (maybe an associative array, for convenience) indicating the user has overridden the default, e.g. x) x="$OPTARG"; user_overrides[x]=true;;
    – cas
    Aug 2, 2023 at 15:17
  • @cas I tend to forget about assoc arrays (except if writing perl); thank you for the reminder Aug 2, 2023 at 16:37

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .