Search code examples
shelltcsh

Tcsh Script Last Exit Code ($?) value is resetting


I am running the following script using tcsh. In my while loop, I'm running a C++ program that I created and will return a different exit code depending on certain things. While it returns an exit code of 0, I want the script to increment counter and run the program again.

#!/bin/tcsh

echo "Starting the script."
set counter = 0

while ($? == 0)
    @ counter ++
    ./auto $counter
end

I have verified that my program is definitely returning with exit code = 1 after a certain point. However, the condition in the while loop keeps evaluating to true for some reason and running.

I found that if I stick the following line at the end of my loop and then replace the condition check in the while loop with this new variable, it works fine.

while ($return_code == 0)
    @ counter ++
    ./auto $counter
    set return_code = $?
end

Why is it that I can't just use $? directly? Is another operation underneath the hood performed in between running my custom program and checking the loop condition that's causing $? to change value?


Solution

  • That is peculiar.

    I've altered your example to something that I think illustrates the issue more clearly. (Note that $? is an alias for $status.)

    #!/bin/tcsh -f
    
    foreach i (1 2 3)
        false
        # echo false status=$status
    end
    echo Done status=$status
    

    The output is

    Done status=0
    

    If I uncomment the echo command in the loop, the output is:

    false status=1
    false status=1
    false status=1
    Done status=0
    

    (Of course the echo in the loop would break the logic anyway, because the echo command completes successfully and sets $status to zero.)

    I think what's happening is that the end that terminates the loop is executed as a statement, and it sets $status ($?) to 0.

    I see the same behavior with both tcsh and bsd-csh.

    Saving the value of $status in another variable immediately after the command is a good workaround -- and arguably just a better way of doing it, since $status is extremely fragile, and will almost literally be clobbered if you look at it.

    Note that I've add a -f option to the #! line. This prevents tcsh from sourcing your init file(s) (.cshrc or .tcshrc) and is considered good practice. (That's not the case for sh/bash/ksh/zsh, which assign a completely different meaning to -f.)

    A digression: I used tcsh regularly for many years, both as my interactive login shell and for scripting. I would not have anticipated that end would set $status. This is not the first time I've had to find out how tcsh or csh behaves by trial and error and been surprised by the result. It is one of the reasons I switched to bash for interactive and scripting use. I won't tell you to do the same, but you might want to read Tom Christiansen's classic "csh.whynot".