last day (21 days later) » 

6:45 AM
10
A: Why does ( echo ""; exit 1) not exit the script?

hermannkExecuting the exit in a subshell is one pitfall. Even more puzzling is thwarting the effect of exit by wrapping it into local builtin like in the following script #!/bin/bash function calc { echo 42; exit 1; } echo $(calc) The script prints 42. Here, the exit does not abort the sc...

 
How is this relevant exactly?
 
@mikeserv: I added an example to show the relevance.
 
i think i get where youre going with it, but i dont see what it has to do with the question - local's not involved. pretty well-written, though, especially after the edit. by the way date ... || exit accomplishes the same as date ...; [ if $? ... ]...else exit 1; fi
oh, and local is an implementation defined builtin - i think the linux group (whatever thats called) requires it, but i dunno if theyre very specific. how it behaves regarding exit is probably up to the shell implemetation.
 
@mikeserv: Yes, you're right. Even terser. But the pitfall is still there.
 
i suppose, but it wouldnt matter in either case because you call the function in a subshell both times. doing fn() { echo output; exit; }; var=$(fn) makes local irrelevant - the function is called in the $(command-subbed) shell and so everything it does is local to its cloned space - including the exit - whether or not local comes into it.
 
6:45 AM
@mikeserv: Sorry, my example was broken. I'd forgotten the test in doit().
 
Hmmm. I think you've got the wrong idea about this whole thing maybe - why should local alter its return in anyway if local var=$(exit 1)? There's no way local could track the return value of the command sub - it is executed and expanded to the output of the command sub before local is. The $(command substitution) is a subshell run by the current shell with its stdout redirected to the value of an argument. local should probably return 0 for either var=something or var=
hi there.
well, maybe not no way it could, but certainly no reason it would. i cant think of many commands that are concerned at all w/ the returns of their args - and that is among the few that have any means of checking.
it is conceivable that - as a builtin - local could check. if you do var=$(false); echo $? youll get the return in some shells of the subshell in $? - but there arent any commands that care.
@hermannk - you in here?
 
@mikeserv yes
I feel local var=$(exit 1) is a good example.
 
...ok?
 
Compare it with pipes. There you can set pipefail to stop the evaluation of pipes when a subshell returns a non-zero result. I am missing this for the local builtin.
Intuitively this flag should be set by default. I admit that my point of view is tainted by my C++ experience. Intuitively —I am aware that intuition is wrong here— local is just a keyword specifying the visibility of a variable just like register in good old C. register variables cannot be global. I am aware that local is a builtin which executes and yields a return value. But overwriting the return value of $(exit 1) is at least puzzling.
I will have to modify my example further to make it clear.
Step 1:
 
7:10 AM
touch $(fname) || exit 1
 
This fails because the return value of the fname function is <b>always</b> overwritten by the return value of touch. Therefore I need to split the commands into two lines. This leads me to step 2:
 
local FNAME=$(fname) || exit 1
touch ${FNAME}
 
I used the local builtin to document that FNAME is just a transient variable with no intended longevity. We know that this implementation fails too because it is not the status of $(fname) that is being tested. This leads me to step 3:
 
local FNAME
FNAME=$(fname) || exit 1
touch ${FNAME}
 
Now everything works as expected but I feel that the solution is too redundant. I would like to have a flag to make solution 1 working. Why should touch be executed after $(fname) has failed?
 
 
1 hour later…
8:34 AM
@hermannk - no touch $(false) failing has nothing at all to do with tue returm value of the subshell. instead it fails because it cannot create or update the mtime of a file with a null filename.ntry touch '' and see for yourself. however a null-val var is perfectly fine - do var= and see that too.
look when you do a subshell the shell clones itself into a separate contained env. it does a pipe. so false | read v is (very loosely) equivalent. in that case read will fail - but not because false fails, in stead because false provides nothing for it to read.
 
8:54 AM
false | read v fails because there is nothing to be read. Okay. So let us feed the beast:
 
h@e4:~/e$ v=x
h@e4:~/e$ echo $v
x
h@e4:~/e$ ( echo y ; true ) | while read v ; do echo $v ; done
y
h@e4:~/e$ echo $v
x
h@e4:~/e$ ( echo y ; false ) | while read v ; do echo $v ; done
y
h@e4:~/e$ echo $v
x
h@e4:~/e$ set -eo pipefail
h@e4:~/e$ ( echo y ; true ) | while read v ; do echo $v ; done
y
h@e4:~/e$ ( echo y ; false ) | while read v ; do echo $v ; done
y

Process shell exited abnormally with code 1
 
right - ( cmd ) is not the same as $(cmd)
they execute in the same environment, but they evaluate at different times.
@hermannk the shell replaces $(cmd) with the output of (cmd) - like an inverted pipe
 
First I set ${v} to x to see whether is has been touched. Then I feed y into the pipe followed by a "good" status. The pipe is consumed by the read-loop as expected. And ${v} is still untouched. Next step is to feed y followed by a "bad" status. As expected, due to the default settings of -e and -o pipefail the read-loop does not care about the status. This changes after bash has been armed.
 
local v=$(false) gets replaced with local v= and so local returns true. try local "$(echo v=; false)"; echo $?; local "$(echo; true)"
@hermannk i dunno what armed means, but pipestatus means the shell has to monitor all of those between processes - that reeks of inefficiency. usually the shell should keep out of a pipeline - that's the point of the pipeline in the first place - direct comms between proc1>2 without intermediate shell involvement.
 
"Armed" here means "care about if something happens, be prepared". I admit that -o pipefail is less efficient and in our trivial examples this is overengineered but in a real-world application it is puzzling if an exit 1 is just ignored.
Technically I understand why bash behaves just like it does. It is not a bug. But its behavior is highly error-prone.
 
9:12 AM
it isnt ignored - but it local the subshell:

fn() if [ "$#" -gt 0 ]
then echo all args:
else exit 1
fi
local "a=$(fn "$@"; printf %s\\n "$@")"
see - the returns matter within ()
and as i said before, you can probably get that w/ v=$(false); echo "$?"
maybe even true; local "v=$(false)" "r=$?"
but that depends on the shell.
bash very often behaves badly - its a colossal sloth of an interpreter. you could do much better. it is all the addins like pipestatus and a array types and other mostly useless extensions - things that should be better handled by the commands the shell is meant to exec - that make it that way.
@hermannk - yeah - this prints 1:
bash -c 'fn(){ local "v=$(false)" "r=$?"; echo "$r"; }; true; fn'
see? the subshell's return is there, but it is checked in that case at var eval time - at the point when bash is replacing $(false) and $? with their values before executing the command line.
so it evals left to right - $(false) first then $?. that means that the return for $() was the last one when $? is evaluated - and so r=1.
 
9:30 AM
Got it.
 
but when local runs $? holds its return, which has nothing at all to do with the evaluation process of its arguments.
 
Just one question: What is the true in your last example good for?
 
oh, just to ensure $?==0 before calling fn.
 
Should be done by echo "$r" already. Am I right?
 
no.
that happens even after local.
but it actually should be done by fn() { ...define...}
 
9:33 AM
Yes, again I forgot the status return of local. You're right.
 
so, yeah, it is not necessary - it would be 0 anyway - it was just to show.
 
Anyway, to return to my starting point. I have a function fname which returns a string and a status. And I want to pass the string to some other function just like in echo $(fname). Isn't there a better way than to declare a local, fill its value and use it as an argument? Three lines just to check the return value of fname?
 
how does it return a string?
no - thats probably more easily done.
whats it look like?
 
Back to fname() {date +"%Y%m%d.log" -d"$1" 2>/dev/null}.
That's where I started.
 
so you want like fn() { do something w/ "$1"; } ; fn "$(fname)"?
positionals are always local and can be set at any time.
 
9:43 AM
What I rather want is fn "$(fname)" || exit 1. It should exit in case of error.
 
or even fn() { set -- "$(date...)" "$@"; [ "$2" = firstarg ] && echo yes; } fn firstarg
you want date to kill it? ok-
fn() { echo "${1:?the first arg is empty!}"; }; fn "$(fname)"
 
This solution relies on an empty string on not on a status. Does it?
This works perfectly for the date example but fails for the general case when a function either returns a string and true or just false (and possibly no string).
 
there is also set -u for that which kills a shell anytime a var is evaluated which is... hmm...? actually can't remember if that only works for unset vars or also for null val vars... but param expansions do if you use :?. no - if it is a solution it relies on a test. if the script is written in such a way that the arg must either be not null or the script should fail then there you go. remember touch ''
 
Look good. I'll try set -u.
 
in those cases you might do: fn() { [ "${1##*.}" -eq 0 ] || exit; }; fn "$(fname; echo ".$?")"
 
9:57 AM
That seems to be a viable solution. Thanks.
 
any time.
 
Thank you for the discussion. I'll review the results and try to rephrase my original post.
 
@hermannk another way to do that last, maybe:
v=$(cmd; echo .$?); : "$((1/!${v##*.}))"; v=${v%.*}
kinda hacky but dividing by zero is a syntax error that kills a shell - so the boolean not of the tail of var either does nothing as it is an arg to the : true command; or the shell dies w/ a divide by 0 err - though that can be eaten with : 2>/dev/null $((math))
 
10:43 AM
Agreed, hacky, mabe too hacky. I prefer solutions which do what they say. That way I have the chance to maintain them after years.
 

  last day (21 days later) »