Bash: assignment of "times" output embedded in a variable

In a bash script, I would like to assign the output of times built-in array variable, but could not find a 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 in the times_a array, because pipelines or $(times) will execute in a subshell and return incorrect values.

Any better solution without a temporary file?

+7
source share
3 answers

The main problem you need to solve is how to get both the execution of time and the assignment of a variable in the same shell without a temporary file. Almost every Bash method provides the ability for a pipeline to output one thing to another or capture command output; it has one side working in a subshell.

Here is one way to do this without a temporary file, but I’ll warn you, it’s not very, it does not migrate to other shells, and this requires at least Bash 4:

 coproc co { cat; }; times 1>&${co[1]}; eval "exec ${co[1]}>&-"; mapfile -tu ${co[0]} times_a 

I will break this for you:

 coproc co { cat; } 

This creates a coprocess; a process that runs in the background, but you are given pipes to talk to standard input and standard output, which are FDs ${co[0]} (standard from cat ) and ${co[1]} (standard in cat ), The commands are executed in a subshell, so we cannot do any of our goals there (run times or read into a variable), but we can use cat to simply pass the input to the output and then use this channel to communicate with times and mapfile in the current shell.

 times >&${co[1]}; 

Run times , redirecting your standard to the standard cat command code.

 eval "exec ${co[1]}>&-" 

Close the input end of the cat . If we do not, cat will continue to wait for input, leaving it open, and mapfile will continue to wait for it, as a result of which your shell will hang. exec , when no commands are passed, simply applies its redirects to the current shell; redirect to - closes FD. We need to use eval because Bash seems to have problems with exec ${co[1]}>&- , interpreting FD as a command instead of the redirect part; using eval allows you to replace this variable first and then execute.

 mapfile -tu ${co[0]} times_a 

Finally, we really read the data from the standard from the coprocess. We managed to run the times and mapfile in this shell and not use temporary files, although we used 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, and not as one command, the latter is unsuccessful; because when you close the standard cat value, it terminates, causing the coprocess to exit, and the FDs must be closed. It seems that when running on just one line, the mapfile runs fast enough so that the coprocess is still open when it starts and, therefore, it can read from the channel; but I can get lucky. I did not understand a good way around this.

All said, it’s much easier to just write a temporary file. I would use mktemp to generate the file name, and if you're in a script, add a trap to make sure you clear your temporary file before exiting:

 tempnam=$(mktemp) trap "rm '$tempnam'" EXIT times > ${tempnam} mapfile -t times_a < ${tempnam} 
+6
source

Wow, good question.

An improvement would be to use mktemp, so you won’t rely on chance to make your file unique.

 TMPFILE=$(mktemp aaa_XXXXXXXXXX) times > "$TMPFILE" mapfile -t times_a < ${tempnam} rm "$TMPFILE" 

Also, I use mapfile instead (because I don't have a mapfile).

 a=0; for var in $(cat "$TMPFILE"); do ((a++)); TIMES_A[$a]=$var; done 

But, yes, I don’t see how you can do this without files or named pipes.

+1
source

One way to do something like this would be

 times > >(other_command) 

This is an output redirect associated with a process change. This times method is executed in the current shell, and the output is redirected to a new subprocess. Therefore, however, doing this using mapfile does not make much sense, since this command will not be executed in the same shell and probably not what you want. The situation is a bit complicated, since you cannot either invoke the shell's built-in shells executed in a subshell.

0
source

All Articles