Use & (ampersand) in single line bash loop

I have been using this command successfully, which changes a variable in a config file and then executes a Python script within a loop:

for((i=114;i<=255;i+=1)); do echo $i > numbers.txt; python DoMyScript.py; done

As each DoMyScript.py instance takes about 30 seconds to run before terminating, I’d like to relegate them to the background while the next one can be spawned.

I have tried what I am familiar with, by adding in an ampersand as below:

for((i=114;i<=255;i+=1)); do echo $i > numbers.txt; python DoMyScript.py &; done

However, this results in the below error:

-bash: syntax error near unexpected token `;'

Answers:

Thank you for visiting the Q&A section on Magenaut. Please note that all the answers may not help you solve the issue immediately. So please treat them as advisements. If you found the post helpful (or not), leave a comment & I’ll get back to you as soon as possible.

Method 1

Drop the ; after &. This is a syntactic requirement

for((i=114;i<=255;i+=1)); do echo $i > numbers.txt;python DoMyScript.py & done

Method 2

Given Stephane’s comment on 1_CR’s answer, you probably want:

for i in {114..255}; do { echo $i > numbers.txt && python DoMyScript.py; } & done

Method 3

Lose the ;:

for((i=114;i<=255;i+=1)); do echo $i > numbers.txt;python DoMyScript.py & done

Method 4

As stated by the other answers:

  • The problem is the ; after the &.
  • Deleting the ; after the & will result in a command
    that will run without shell syntax errors. 
    However, it is unlikely to function correctly,
    because it creates a race condition.

The OP stated that he resolved the race condition by adding a 3 second delay.

Nobody mentioned:

  • A 3 second delay might work today. 
    Tomorrow, the computer might be more sluggish, and it could fail again. 
    Next year, the script might be modified to be more time-consuming,
    and it could fail again. 
    The probability of failure goes down if you increase the delay
    by an order of magnitude, by which I mean 30 seconds. 
    Of course this means that the script processes will (probably / presumably)
    be running essentially sequentially, and not concurrently (in parallel),
    thus defeating the purpose of making the processes asynchronous.
  • If running multiple instances of the script is a requirement,
    it’s probably best to modify the script to accept the number parameter
    on the command line:
    for ((i=114;i<=255;i++)); do python DoMyScript.py --number="$i" & done

    and do away with the file.

  • If changing the script is not an option, this solution should work:
    for i in {114..125}; do ( subdir="dir.$i" && mkdir "$subdir" && cd "$subdir" && 
              echo "$i" > numbers.txt && python ../DoMyScript.py; cd .. && rm -r "$subdir" ) & done

    This gives each script process a separate numbers.txt file,
    by giving it a separate directory to run in. 
    The directory is created and deleted, and the script is run, in a subshell.

    • If DoMyScript.py accesses any files other than numbers.txt
      by relative pathnames,
      the above command will need to be adjusted to accommodate that.
    • The above was inspired by glenn jackman’s answer
      (although I believe that mine will work and his won’t).
    • If command B depends on the success of command A,
      then A && B is better defensive programming
      than AB
      But it’s probably not the ideal way to handle this situation. 
      If mkdir dir.114 fails, then the next 111 attempts are likely to fail also,
      and you’ll get 112 error messages. 
      It would be better to abort the loop if a fatal error occurs.

      The fact that the action is happening in an asynchronous subshell
      makes this somewhat tricky.

      for i in {114..125}; do
              { subdir="dir.$i" && mkdir "$subdir" && echo "$i" > "$subdir"/numbers.txt; } || break;
              ( cd "$subdir" && python ../DoMyScript.py; cd .. && rm -r "$subdir" ) & done

      will cause the loop to abort if a mkdir
      or an echo value > file command fails.

    • It might be better to work in a directory
      that is ‘‘guaranteed’’ to be writable, like /tmp
      However, this increases the risk that your command
      will collide (interfere) with some other process.
      • You can mitigate this by adding $$ to the directory name;
        e.g., dir.$$.$i or even dir.$BASHPID.
      • This would not eliminate the risk that the mkdir
        or the file creation might fail because the filesystem is full.
    • Note that the above code will keep on going
      and remove the temporary directory even if the script fails. 
      You might want to do something else in that situation.
      • Oops. 
        If, for some reason,
        the mkdir "$subdir" succeeds but the cd "$subdir" fails,
        this will go ahead and do cd .. && rm -r "$subdir"
        If there is a directory with a name like dir.114 in your parent directory
        (i.e., parallel to your current directory), it will be removed. 
        I believe that you can (at least somewhat) fix this
        by changing the last line to
        ( cd "$subdir" && { python ../DoMyScript.py; cd ..; } && rm -r "$subdir" ) & done

        or by using absolute paths; e.g., subdir="$PWD/dir.$i".


All methods was sourced from stackoverflow.com or stackexchange.com, is licensed under cc by-sa 2.5, cc by-sa 3.0 and cc by-sa 4.0

0 0 votes
Article Rating
Subscribe
Notify of
guest

0 Comments
Oldest
Newest Most Voted
Inline Feedbacks
View all comments
0
Would love your thoughts, please comment.x
()
x