0
for i in $( seq 1 $maxTry )
do
  npm publish $package
  
  if grep -q "npm ERR!" # if there are any NPM errors
  then
     if [ [grep -q "code ENOTFOUND" "publish log"] && [ ! $i -eq $maxTry ] ];
     then 
        continue
     else
        break
   fi

I would like to retry the for-loop for $maxTry-amount of time only if:

  1. the NPM Error is caused by the network error
  2. the amount of retry time has not exceeded the $maxTry

Otherwise, it will break the loop. For example,

  1. There is a NPM error, but not caused by the network error
  2. Although it is a network error, it has reached the end of the loop

I am keep having a syntax error in if [ [grep -q "code ENOTFOUND" "publish log"] && [ ! $i -eq $maxTry ] ]. I am confused whether it is okay to put grep -q inside the if statement's bracket. Is this the right syntax?

whoaji
  • 1
  • No, `if grep ...` is the right syntax, already used in the fist if. But the first grep is missing a file. – choroba Oct 11 '22 at 20:42
  • 2
    The `[` isn't a property of `if`, it isn't part of the syntax. It is a command which is used to check file types and compare values. – M. Nejat Aydin Oct 11 '22 at 20:52
  • 1
    In addition to the problems with `[ ]`, I think you're also confused about how `grep` works. You need to provide it with something to search, either by filename (`grep pattern fileToSearch`), or via standard input (`grep pattern – Gordon Davisson Oct 11 '22 at 21:34
  • If `npm publish` is well-behaved, you can use `if npm publish "$package"; then echo "publishing $package succeeded"; else echo "publishing $package failed"; fi` – Charles Duffy Oct 11 '22 at 21:44

1 Answers1

1

[...] is not a structure operator. It is a command. Like grep is. Well, it is nowadays a built-in command, but yet, still a command. Back in the time, [ was litteraly a command. A unix program. That you could find in /usr/bin/[ (it is still there by the way. ls it on your PC. but not for bash, which has its own builtin [). In fact, it was then a link to /usr/bin/test. A command whose purpose is to test things.

It takes some arguments. Does some computation. And has an exit code 0 or 1, whether this computation decides it succeeds or not.

For example, you can try

$ /usr/bin/test a = b
$ echo $?
1
$ /usr/bin/test a = a
$ echo $?
0
$ /usr/bin/test -f .bashrc
0
$ /usr/bin/test -f anonexistingfile
1

One notable difference between /usr/bin/test and /usr/bin/[ is that /usr/bin/[ raises an error if the last argument is not ]. Just of aesthetic reasons.

So, don't use [ like a sort of parenthesis for grouping logic. Don't nest them. Don't use them to enclose other commands. It makes no sense. Use [ as you would use an external program, to which you pass arguments, and from which you expect an exit code.

Exactly like you to with grep -q.

&& is an operator separating two commands. Like ; is. With a different behavior than ;. a && b runs command a. And then runs command b, only if a exit code what 0. And the exit code of a && b is 0 iff exit code of both commands a and b where 0. Other wise it is the exit code of the first of them that failed.

And, lastly, if is used with a command.

if command0
then
    command1
else
    command2

Executes command0. And then executes command1 if command0 was successful (had an exit code 0), or command2 otherwise.

So, with that in mind, in your case, your line

if [ [grep -q "code ENOTFOUND" "publish log"] && [ ! $i -eq $maxTry ] ];

should be

if grep -q "code ENOTFOUND" "publish log" && [ ! $i -eq $maxTry ] ;
then
...

All my historical introduction shows you that it is just the same as

if grep -q "code ENOTFOUND" "publish log" && test ! $i -eq $maxTry  ;
then
...

It Executes command grep -q "code ENOTFOUND" "publish log"

  • if it fails (exit code non-zero), then does not execute [ ! $i -eq $maxTry ]. And the whole if "condition" fails, and the else part is executed
  • if it succeeds (exit code 0), executes command [ ! $i -eq $maxTry ]
    • if it fails, the whole if "condition" fails, and the else part is executed
    • if it succeeds, the whole if "condition" is a success, and the then part is executed.

Of course, the choice of [ for that command name, and the choice of requiring that it terminates by a ] is to makes it look a little bit like classical condition in other languages. But to understand this correctly, and not to struggle about when to add some [, it is important that you understand what if, what && and what [ mean in bash. if and && are deciding if and when to execute several commands, and what exit code the group of several commands has. [ aka test is just one of the many command you may want to execute. A command that does nothing, except having an exit code.

Last remark: I don't comment on the rest of your code. Only on the line about which was your question. For example,

grep -q "code ENOTFOUND" "publish log"

that I copied&pasted from your code to mine, because you gave no hint about what it is supposed to do. But I have some doubt that this is what you want. Isn't there a missing dot between publish and log here?

The -q is right. It mean that grep will, like [ a command that does nothing (prints nothing) and just have an exit code. So suited for usage in a if. But then, you seems to be looking for a string "code ENOTFOUND" in a file named "publish log".

Likewise, I am pretty sure

if grep -q "npm ERR!"

is not what you wanted. It waits for lines on standard input, and succeeds (has exit code 0) if one of them contains "npm ERR!". I am pretty sure you intended to grep the content of a file. Like maybe the same publish.log as earlier.

And more generally I don't know exactly what you are trying to do. But I have the feeling that you are just trying to npm publish something over and over, until either it succeeds or you reach a maximum trial. In which case

for ((i=0; i<$maxTry; i++))
do
   npm publish $package
   grep -q "code ENOTFOUND" publish.log || break
done

probably does it.

Maybe npm also has an exit code (well, it sure has. I just don't know whether it works for you. It returns 0 on success and non-0 on failure), and you could just do

for ((i=0; i<$maxTry; i++))
do
   if npm publish $package
   then
      break
   fi
done

or

for ((i=0; i<$maxTry; i++)); do
   npm publish $package && break
done

tl;dr (but if you don't want to always struggle with that kind of thing, that tl;dr won't cut it. It is giving a fish instead of teaching fishing): correct syntax is

if grep -q "code ENOTFOUND" "publish log" && [ ! $i -eq $maxTry ] ;
chrslg
  • 9,023
  • 5
  • 17
  • 31