In a bash script I would like to assign the output of the times builtin to an array variable, but I found no better way than
tempnam=/tmp/aaa_$$_$RANDOM
times > ${tempnam}
mapfile -t times_a < ${tempnam}
I write the output to a temp file and read it back in array times_a because pipelines or $(times) would execute in a subshell and return the wrong values.
Any better solution without the temp file?
The fundamental problem that you need to solve is how to get both the execution of
timeand the variable assignment to happen in the same shell, without a temporary file. Almost every method Bash provides of piping the output of one thing to another, or capturing the output of a command, has one side working in a subshell.Here is one way you can do it without a temporary file, but I’ll warn you, it’s not pretty, it’s not portable to other shells, and it requires at least Bash 4:
I’ll break this down for you:
This creates a coprocess; a process which runs in the background, but you are given pipes to talk to its standard input and standard output, which are the FDs
${co[0]}(standard out ofcat) and${co[1]}(standard in ofcat). The commands are executed in a subshell, so we can’t do either of our goals in there (runningtimesor reading into a variable), but we can usecatto simply pass the input through to the output, and then use that pipe to talk totimesandmapfilein the current shell.Run
times, redirecting its standard out to the standard in of thecatcommand.Close the input end of the
catcommand. If we don’t do this,catwill continue waiting for input, keeping its output open, andmapfilewill continue waiting for that, causing your shell to hang.exec, when passed no commands, simply applies its redirections to the current shell; redirecting to-closes an FD. We need to useevalbecause Bash seems to have trouble withexec ${co[1]}>&-, interpreting the FD as the command instead of part of the redirection; usingevalallows that variable to be substituted first, and then then executed.Finally we actually read the data from the standard out of the coprocess. We’ve managed to run both the
timesand themapfilecommand in this shell, and used no temporary files, though we did use a temporary process as a pipeline between the two commands.Note that this has a subtle race. If you execute these commands one by one, instead of all as one command, the last one fails; because when you close
cats standard in, it exits, causing the coprocess to exit and FDs to be closed. It appears that when executed all on one line,mapfileis executed quickly enough that the coprocess is still open when it runs, and thus it can read from the pipe; but I may be getting lucky. I haven’t figured out a good way around this.All told, it’s much simpler just to write out the temp file. I would use
mktempto generate a filename, and if you’re in a script, add a trap to ensure that you clean up your tempfile before exiting: