-1

I have at the moment a bash script (merge.sh) and I made another one that inside the new one (merge-all.sh) there is a loop that calls the merge.sh. I want to see if it is possible to condition one of the commands inside merge.sh to run only in the first iteration, since it is a waste to call it every time.

I could move it on the merge-all.sh, but I want merge.sh to be functional with/out the other script.

The merge.sh file is the following and the command in question is the source init:

#!/bin/sh
#
source /path/init
cd /path_2


INPUT_LIST=/path_3/$1
echo "-----------------------------"
echo "Starting script:" $(basename $BASH_SOURCE)
echo "----------------------------- 0"
echo ${INPUT_LIST}

#hadd -f ${INPUT_LIST%.txt}.root @${INPUT_LIST}
John Kugelman
  • 349,597
  • 67
  • 533
  • 578
  • 1
    Why don't you just call it once instead of a loop ? by the way, you did not provide merge-all.sh, which make it impossible to fix. – Itération 122442 Aug 10 '23 at 12:01
  • What is the question? sorry I don't follow... , Also have tried https://shellcheck.net for validation/recommendation? – Jetchisel Aug 10 '23 at 12:20
  • If I understand correctly, ... `merge.sh` is called multiple times and you want to modify `merge.sh` so that it only runs `source /path/init` the first time it is called .... is this a correct understanding of what you wish to do? – markp-fuso Aug 10 '23 at 12:28
  • 1
    please update the question with an example of *how* `merge-all.sh` calls `merge.sh`; if you are *executing* `merge.sh` then the `source /path/init` is being called in a subshell ... and changes to variables in subshell's are not passed 'up' to the calling/parent process ... net result is that for each *execution* of `merge.sh` you *do* need to run `source /path/init`; on the other hand ... – markp-fuso Aug 10 '23 at 12:31
  • if `merge.sh` is being *sourced* (ie, `merge-all.sh` is running `source merge.sh` or `. merge.sh`) then the code within `merge.sh` is running within the same shell as the calling/parent process and in this case you would only need to `source /path/init` once; one idea for insuring `source /path/init` is only called once ... and assuming `init` includes some unique variables ... is to have a test in `merge.sh` that looks for one of these variables being set (eg, `[[ -n "${var_name}" ]]`) and if 'false' then `source /path/init`; again, this only works if `merge-all.sh` is *sourcing* `merge.sh` – markp-fuso Aug 10 '23 at 12:36
  • If you want the first loop iteration to do something special, unwind the first loop iteration… – Andrej Podzimek Aug 10 '23 at 12:39
  • 1
    Much better would be to rewrite `merge.sh` so it takes one argument per operation, so you can make one call do multiple things. We really need `merge-all` to be included in the question so we can suggest better improvements (ones that modify the calling convention); it's far better practice to either extend the parent process to do state management there, or push the loop down into the child. Only including half the information restricts the set of plausible answers to what I'd categorize as bad ones. – Charles Duffy Aug 10 '23 at 13:08

2 Answers2

1

Check for a behavior flag such as a variable.

$: cat outer
#! bash
for o in {1..3}
do . inner
done

$: cat inner
#! bash
((first++)) || echo on record $o
date -d "+$o days"

$: ./outer
on record 1
Fri Aug 11 08:08:12 CDT 2023
Sat Aug 12 08:08:12 CDT 2023
Sun Aug 13 08:08:12 CDT 2023

The reverse logic isn't hard either.

$: cat inner
#! bash
if ((skip))
then skip=0
else echo $o
fi

$: ./outer
1
2
3

$: skip=1 ./outer
2
3

A more complicated implementation might use command-line options, but it's easy enough to integrate behavior if both scripts are designed for it, and still leave the "inner" script useful as a standalone.

Paul Hodges
  • 13,382
  • 1
  • 17
  • 36
0

I THINK what you may really want is to have a script merge.sh that can be called any time to do X (where X is anything that should be done once, e.g. maybe start a daemon) but that script should not do X again if it's already done X. Given that, the fact it's being called in a loop sometimes is irrelevant but framing the question as "I have a command called from a loop" makes us think that the obvious solution is to just not call it in a loop. The only clue that that might not be the best solution is your statement that I want merge.sh to be functional with/out the other script.

If I'm right then the solution would be to check if X has already been done rather than checking if this is the first time merge.sh has been called, e.g. by testing for some process running or some environment variable having some value or some value set in some file (and, if the latter, then manage that somehow to ensure the value is cleared when necessary).

For example, I could imagine you having something like this:

$ cat start_foo

    foo &

$ cat start_bar

    bar &

$ cat start_all

    while :; do
        start_foo
        start_bar
        sleep 60
    done

$ tail -1 .profile:

    start_all &

so you have a script start_all that runs continuously in the background making sure some set of other background processes are running and so it calls a script per process (e.g. start_foo) to ensure that process (foo) is running.

You don't want to start a new foo process every minute but you also don't want to clutter start_all with the details of what needs to happen to ensure foo is running. Given that, you could write start_foo as (pseudo-code):

$ cat start_foo:

    if ! pgrep foo; then
        foo &
    fi

That will effective give you what you want, i.e. a script that only does something, foo &, the first time it's called (in normal operation where foo doesn't die) and will work whether called from a loop or not.

Whether the check in start_foo is for a process running or a file existing or anything else is up to whatever start_foo or foo actually does.

In your case you MAY want something like:

#!/bin/sh

INPUT_LIST=/path_3/$1
outfile=${INPUT_LIST%.txt}.root

if [ ! -s "$outfile" ]; then

    source /path/init
    cd /path_2
    
    echo "-----------------------------"
    echo "Starting script:" $(basename $BASH_SOURCE)
    echo "----------------------------- 0"
    echo ${INPUT_LIST}
    
    hadd -f "$outfile" @${INPUT_LIST}
fi

Check your quotes and capitalization btw - run your script through http://shellcheck.net and read Correct Bash and shell script variable capitalization.

You may also want something like:

$ cat merge.sh
#!/bin/sh

ucl=$2
INPUT_LIST=/path_3/$1
outfile=${INPUT_LIST%.txt}.root

if [ "$ucl" = 1 -o ! -s "$outfile" ]; then

    source /path/init
    cd /path_2
    
    echo "-----------------------------"
    echo "Starting script:" $(basename $BASH_SOURCE)
    echo "----------------------------- 0"
    echo ${INPUT_LIST}
    
    hadd -f "$outfile" @${INPUT_LIST}
fi

and call it as:

ucl=1
while :; do
    merge.sh whatever "$ucl"
    ucl=""
done

or some other variation.

Ed Morton
  • 188,023
  • 17
  • 78
  • 185