36

I want to use a bash variable to indicate a file descriptor, like this:

id=6
file=a
exec $id<>$file

But the usage is wrong:

-bash: exec: 6: not found

So, how to use a variable to indicate a file descriptor in exec command?

codeforester
  • 39,467
  • 16
  • 112
  • 140
WH's HeV
  • 445
  • 1
  • 5
  • 9

4 Answers4

34

The accepted answer is correct, but as of bash 4.1, you can use automatic file descriptor allocation, and in that case you don't need eval:

file=a
exec {id}<>"$file"

Then you can use it like this:

echo  test >&${id}

or:

fsck -v -f -C ${id} /dev/something
Paul Tobias
  • 1,962
  • 18
  • 18
  • It seems that "id" must be fixed in the code. It is possible to make "id" a variable? This would allow more flexibility. Imagine I am using a code like `for (( FD=3 ; FD < 100 ; FD++ )) ; do exec {FD}> file.$FD ; echo $FD >&${FD}; done`. This would not work, since `exec {FD}> file.${FD}` would be the same descriptor over all values of $FD, right? – Denio Mariz Jan 26 '16 at 20:33
  • 2
    `id` is a variable, you can see it with `echo $id`. – Paul Tobias Jan 29 '16 at 10:05
  • 4
    Nice. To close such descriptor later use `exec {id}<&-`. – Piotr Findeisen Apr 06 '16 at 13:42
  • 1
    Or is it closed automatically as written here? http://unix.stackexchange.com/questions/181937/how-create-a-temporary-file-in-shell-script – FarO Jun 12 '16 at 17:54
  • 2
    Arrays can also be used. `declare -a PIPE; exec {PIPE[0]}<>/dev/something`, e.g. `echo Hello World\!>&${PIPE[0]}` and close with `exec {PIPE[0]}>&-` – Drew Chapin Mar 01 '18 at 17:28
  • @DenioMariz it's possible to use an array to open 100 file descriptors: `for i in {1..100}; do exec {filedescriptors[$i]}<>file.$i; echo $i >&"${filedescriptors[$i]}"; done` see [jan's answer](https://stackoverflow.com/a/51137057/857626) for details – Paul Tobias Apr 18 '19 at 06:53
  • In my tests on 4.1, you may not use associative array item to seed {id}, so exec {FileDescriptorList[Five]} failed where FileDescriptorList[Five] is 5 – spioter May 03 '19 at 18:58
  • @spioter this is bash 4.3. But `local -n` works: `x() { local -n a="$1" b="$2" c="$3"; exec {a}<&0 {b}>&1 {c}>&2; }` then `declare -A A; x "A[t1]" "A[t2]" "A[t3]"` or `x=a1 y=a2 z=a3 x "$x" "$y" "$z"` (which gives us something like `{!x}<&0` which isn't in bash – Tino Jul 15 '19 at 12:35
  • Sorry, can no more edit previous: `local -n` needs bash 4.3 and above, too, so it only gives us something like `{!var}` – Tino Jul 15 '19 at 12:55
  • 1
    @FarO: I use this to `export BASH_XTRACEFD=${id}`. When you later unset BASH_XTRACFD the file descriptor will be closed. https://www.gnu.org/software/bash/manual/html_node/Bash-Variables.html – Andreas N Jul 16 '20 at 17:37
21

You have to use eval and put the entire expression in quotes.

eval "exec $id<>$file"

And do that every time you want to use $id.

eduffy
  • 39,140
  • 13
  • 95
  • 92
  • 2
    You don't need to `eval` all usages, just the `exec` call to open the descriptor. There's examples of using a variable to reference a file descriptor in [this question](https://stackoverflow.com/q/41603787/113632). – dimo414 Jun 23 '20 at 07:25
  • @dimo414, interesting! Apparently since Bash 4.1, you can do [exec {varname}<&-](https://stackoverflow.com/a/41620630/6232717) and the like! I had no clue they (somewhat) fixed that annoyance – Jeffrey Cash Jul 25 '20 at 06:19
  • 1
    fyi: `$id<$file # INPUT mode` `$id>$file # OUTPUT mode` `$id>>$file # APPEND mode` `$id<>$file # INPUT and OUTPUT mode` – kaluzki Oct 23 '20 at 08:29
1

In the specific case of closing the descriptor, you can put {} around the variable.

exec {id}>&-

The Bash Manual says:

If >&- or <&- is preceded by {varname}, the value of varname defines the file descriptor to close.

Barmar
  • 741,623
  • 53
  • 500
  • 612
0

I found the discussion in the answer of tobias.pal very interesting: https://stackoverflow.com/a/32689974/1184842

for (( FD=3 ; FD < 100 ; FD++ )) ; do exec {FD}> file.$FD ; echo $FD >&${FD}; done

This would not work, since exec {FD}> file.${FD} would be the same descriptor over all values of $FD, right? (Denio Mariz)

I solved this by using an array as stated by Drew Chapin:

#!/bin/bash
# global variables for temp file handling
declare -a TEMPORARY_FILES_WRITE;
declare -a TEMPORARY_FILES_READ;

function createTempFile() {
    local usecase="$1"
    local id="$2"
    local tmpfile=$(mktemp)  # Create a temporal file in the default temporal folder of the system

    # Lets do some magic for the tmpfile to be removed when this script ends, even if it crashes
    exec {TEMPORARY_FILES_WRITE[$id]}>"$tmpfile"
    exec {TEMPORARY_FILES_READ[$id]}<"$tmpfile"
    rm "$tmpfile"  # Delete the file, but file descriptors keep available for this script
}    

for (( FD=3 ; FD < 100 ; FD++ )) ; do 
    TEMP_FILE_COUNTER=$((TEMP_FILE_COUNTER + 1))
    createTempFile "Iteration $FD" $FD ;
    echo $FD >&${TEMPORARY_FILES_WRITE[$FD] ;
done

example=$(cat <&${TEMPORARY_FILES_READ[50]})
echo $example

This will output 50.

jan
  • 2,741
  • 4
  • 35
  • 56