いつ追加のファイル記述子を使用しますか?


74

ファイル記述子を作成して、出力をリダイレクトできることを知っています。例えば

exec 3<> /tmp/foo # open fd 3.
echo a >&3 # write to it
exec 3>&- # close fd 3.

ただし、ファイル記述子がなくても同じことができます。

FILE=/tmp/foo
echo a > "$FILE"

追加のファイル記述子を使用する必要がある場合の良い例を探しています。

回答:


50

ほとんどのコマンドには、単一の入力チャネル(標準入力、ファイル記述子0)と単一の出力チャネル(標準出力、ファイル記述子1)があります。または、それ自体が開く複数のファイルを操作します(したがって、ファイル名を渡します)。(さらに、標準エラー(fd 2)に加えて、通常はユーザーに至るまでフィルター処理されます。)ただし、複数のソースまたは複数のターゲットからフィルターとして機能するコマンドがあると便利な場合があります。たとえば、ファイル内の奇数行を偶数行から分離する簡単なスクリプトを次に示します。

while IFS= read -r line; do
  printf '%s\n' "$line"
  if IFS= read -r line; then printf '%s\n' "$line" >&3; fi
done >odd.txt 3>even.txt

ここで、奇数行と偶数行に異なるフィルターを適用するとします(ただし、それらを元に戻さないと、別の問題になり、一般的なシェルでは実行できません)。シェルでは、パイプを使用してコマンドの標準出力を別のコマンドにのみパイプできます。別のファイル記述子をパイプするには、まずfd 1にリダイレクトする必要があります。

{ while  done | odd-filter >filtered-odd.txt; } 3>&1 | even-filter >filtered-even.txt

もう1つの単純なユースケースは、コマンドのエラー出力のフィルタリングです。

exec M>&Nスクリプトの残りのために(または別のそのようなコマンドがファイル記述子を再び変更するまで)ファイル記述子を別の記述子にリダイレクトします。exec M>&Nとの間の機能にはいくつかの重複がありsomecommand M>&Nます。execフォームは、それが入れ子にする必要がないことで、より強力です。

exec 8<&0 9>&1
exec >output12
command1
exec <input23
command2
exec >&9
command3
exec <&8

他の興味深い例:

そして、さらに多くの例:

PSこれは、fd 3によるリダイレクトを使用するサイトで最も支持された投稿の著者からの驚くべき質問です!


私はむしろ「ほとんどのコマンドにはシングルまたはダブルの出力チャネルがあります -stdout(fd 1)、非常に頻繁にstderr(fd 2)」。
rozcietrzewiacz

また、なぜあなたが使用する理由を説明できますwhile IFS= read -r line;か?私が見ているように、1つの変数()のみに値を割り当てるため、IFSはここでは効果がありません。この質問をご覧ください。
-rozcietrzewiacz

@rozcietrzewiacz stderrについて言及しましたが、1 IFSつの変数に読み取りを行っても(先頭の空白を保持するために)違いが生じる理由についての私の答えの最初の部分を参照してください。
ジル

あなたも同じことをできませんでしたsed -ne 'w odd.txt' -e 'n;w even.txt'か?
ワイルドカード

1
@Wildcard確かに他のツールでも同じことができます。しかし、この答えの目標は、シェルでリダイレクトを説明することでした。
ジル

13

bashスクリプトのチャットコントロールとして追加のFDを使用する例を次に示します。

#!/bin/bash

log() {
    echo $* >&3
}
info() {
    echo $* >&4
}
err() {
    echo $* >&2
}
debug() {
    echo $* >&5
}

VERBOSE=1

while [[ $# -gt 0 ]]; do
    ARG=$1
    shift
    case $ARG in
        "-vv")
            VERBOSE=3
        ;;
        "-v")
            VERBOSE=2
        ;;
        "-q")
            VERBOSE=0
        ;;
        # More flags
        *)
        echo -n
        # Linear args
        ;;
    esac
done

for i in 1 2 3; do
    fd=$(expr 2 + $i)
    if [[ $VERBOSE -ge $i ]]; then
        eval "exec $fd>&1"
    else
        eval "exec $fd> /dev/null"
    fi
done

err "This will _always_ show up."
log "This is normally displayed, but can be prevented with -q"
info "This will only show up if -v is passed"
debug "This will show up for -vv"

8

名前付きパイプ(fifo)のコンテキストでは、追加のファイル記述子を使用すると、ノンブロッキングパイピング動作を有効にできます。

(
rm -f fifo
mkfifo fifo
exec 3<fifo   # open fifo for reading
trap "exit" 1 2 3 15
exec cat fifo | nl
) &
bpid=$!

(
exec 3>fifo  # open fifo for writing
trap "exit" 1 2 3 15
while true;
do
    echo "blah" > fifo
done
)
#kill -TERM $bpid

参照:スクリプトで名前付きパイプが途中で閉じますか?


1
あなたは私の古い質問の1つを掘り出しました:) chadは正しい、あなたは競合状態に陥ります。
n0pe

6

追加のファイル記述子は、変数の標準出力をキャッチしたいが、まだbashスクリプトのユーザーインターフェイスなどで画面に書き込みたい場合に適しています。

arg1 string to echo 
arg2 flag 0,1 print or not print to 3rd fd stdout descriptor   
function ecko3 {  
if [ "$2" -eq 1 ]; then 
    exec 3>$(tty) 
    echo -en "$1" | tee >(cat - >&3)
    exec 3>&- 
else 
    echo -en "$1"  
fi 
}

2
私はこれが新しい答えではないことを知っていますが、それが何であるかを見るためにしばらくこれを見つめなければならず、誰かがこの関数の使用例を追加した場合に役立つだろうと考えました。コマンド-df、この場合。dl.dropboxusercontent.com/u/54584985/mytest_redirect
ジョー


1

例:flockを使用して、スクリプトを強制的にファイルロックで連続的に実行する

1つの例は、ファイルロックを使用して、システム全体でスクリプトを強制的にシリアルに実行することです。これは、同じ種類の2つのスクリプトを同じファイルで操作したくない場合に便利です。そうしないと、2つのスクリプトが相互に干渉し、データが破損する可能性があります。

#exit if any command returns a non-zero exit code (like flock when it fails to lock)
set -e

#open file descriptor 3 for writing
exec 3> /tmp/file.lock

#create an exclusive lock on the file using file descriptor 3
#exit if lock could not be obtained
flock -n 3

#execute serial code

#remove the file while the lock is still obtained
rm -f /tmp/file.lock

#close the open file handle which releases the file lock and disk space
exec 3>&-

ロックとロック解除を定義して、flockを機能的に使用します

このロック/ロック解除ロジックを再利用可能な関数にラップすることもできます。次のtrapシェルビルトインは、スクリプトが終了すると(エラーまたは成功)ファイルロックを自動的に解除します。 trapファイルロックをクリーンアップするのに役立ちます。/tmp/file.lock複数のスクリプトがロックを試みることができるように、パスはハードコーディングされたパスである必要があります。

# obtain a file lock and automatically unlock it when the script exits
function lock() {
  exec 3> /tmp/file.lock
  flock -n 3 && trap unlock EXIT
}

# release the file lock so another program can obtain the lock
function unlock() {
  # only delete if the file descriptor 3 is open
  if { >&3 ; } &> /dev/null; then
    rm -f /tmp/file.lock
  fi
  #close the file handle which releases the file lock
  exec 3>&-
}

上記のunlockロジックは、ロックが解除される前にファイルを削除することです。これにより、ロックファイルがクリーンアップされます。ファイルが削除されたため、このプログラムの別のインスタンスはファイルロックを取得できます。

スクリプトでのロックおよびロック解除機能の使用

次の例のようにスクリプトで使用できます。

#exit if any command returns a non-zero exit code (like flock when it fails to lock)
set -e

#try to lock (else exit because of non-zero exit code)
lock

#system-wide serial locked code

unlock

#non-serial code

ロックできるようになるまでコードを待機させたい場合は、次のようにスクリプトを調整できます。

set -e

#wait for lock to be successfully obtained
while ! lock 2> /dev/null; do
  sleep .1
done

#system-wide serial locked code

unlock

#non-serial code

0

具体例として、サブコマンドからのタイミング情報を必要とするスクリプトを作成しました。追加のファイル記述子を使用するとtime、サブコマンドのstdoutまたはstderrを中断することなく、コマンドのstderr をキャプチャできました。

(time ls -9 2>&3) 3>&2 2> time.txt

これは、point lsのstderrをfd 3に、point fd 3をスクリプトのstderrに、point timeのstderrをファイルにポイントします。スクリプトを実行すると、そのstdoutとstderrはサブコマンドと同じになり、通常どおりリダイレクトできます。timeの出力のみがファイルにリダイレクトされます。

$ echo '(time ls my-example-script.sh missing-file 2>&3) 3>&2 2> time.txt' > my-example-script.sh
$ chmod +x my-example-script.sh 
$ ./my-example-script.sh 
ls: missing-file: No such file or directory
my-example-script.sh
$ ./my-example-script.sh > /dev/null
ls: missing-file: No such file or directory
$ ./my-example-script.sh 2> /dev/null
my-example-script.sh
$ cat time.txt

real    0m0.002s
user    0m0.001s
sys 0m0.001s
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.