ファイル記述子に書き込むのではなく、ファイルに直接コマンドを印刷するのと同じですか?
イラスト
ファイルに直接書き込む:
for i in {1..1000}; do >>x echo "$i"; done
fdを使用してください:
exec 3>&1 1>x
for i in {1..1000}; do echo "$i"; done
exec 1>&3 3>&-
後者はより効率的ですか?
答え1
exec
ループの前にファイルを開くコマンドを使用することとループ内にリダイレクトを置くことの主な違いは、前者はファイル記述子を一度だけ設定することですが、後者はループが繰り返されるたびにファイルを開閉します。
一度実行する方が効率的ですが、ループ内で外部コマンドを実行すると、コマンドの開始に伴うコスト差がなくなることがあります。 (echo
おそらくここに組み込まれているので適用されません)
出力が通常のファイルではなくファイルに転送される場合(x
名前付きパイプなど)、ファイルの開閉操作が他のプロセスに表示される可能性があるため、動作に違いがある可能性があります。
commandによるリダイレクトとcommandによるリダイレクトの間には実際に違いはありません。exec
どちらもファイルを開き、ファイル記述子番号を処理します。
これらの2つはopen()
ファイルとファイルwrite()
であるため、ほぼ同じでなければなりません。 (ただし、命令実行中はfd 1が異なるように保存されます。)
for i in {1..1000}; do
>>x echo "$i"
done
for i in {1..1000}; do
exec 3>&1 1>>x # assuming fd 3 is available
echo "$i" # here, fd 3 is visible to the command
exec 1>&3 3>&-
done
答え2
はい、それはより効率的です。
これをテストする最も簡単な方法は、数を500000に増やし、時間を測定することです。
> time bash s1.sh; time bash s2.sh
bash s1.sh 16,47s user 10,00s system 99% cpu 26,537 total
bash s2.sh 10,51s user 3,50s system 99% cpu 14,008 total
write
strace(1)は理由を明らかにします(代わりに単純なopen
+5 * fcntl
+2 * dup
+2 * close
+がありますwrite
):
私達は次for i in {1..1000}; do >>x echo "$i"; done
を得ます:
open("x", O_WRONLY|O_CREAT|O_APPEND, 0666) = 3
fcntl(1, F_GETFD) = 0
fcntl(1, F_DUPFD, 10) = 10
fcntl(1, F_GETFD) = 0
fcntl(10, F_SETFD, FD_CLOEXEC) = 0
dup2(3, 1) = 1
close(3) = 0
write(1, "997\n", 4) = 4
dup2(10, 1) = 1
fcntl(10, F_GETFD) = 0x1 (flags FD_CLOEXEC)
close(10) = 0
open("x", O_WRONLY|O_CREAT|O_APPEND, 0666) = 3
fcntl(1, F_GETFD) = 0
fcntl(1, F_DUPFD, 10) = 10
fcntl(1, F_GETFD) = 0
fcntl(10, F_SETFD, FD_CLOEXEC) = 0
dup2(3, 1) = 1
close(3) = 0
write(1, "998\n", 4) = 4
dup2(10, 1) = 1
fcntl(10, F_GETFD) = 0x1 (flags FD_CLOEXEC)
close(10) = 0
open("x", O_WRONLY|O_CREAT|O_APPEND, 0666) = 3
fcntl(1, F_GETFD) = 0
fcntl(1, F_DUPFD, 10) = 10
fcntl(1, F_GETFD) = 0
fcntl(10, F_SETFD, FD_CLOEXEC) = 0
dup2(3, 1) = 1
close(3) = 0
write(1, "999\n", 4) = 4
dup2(10, 1) = 1
fcntl(10, F_GETFD) = 0x1 (flags FD_CLOEXEC)
close(10) = 0
open("x", O_WRONLY|O_CREAT|O_APPEND, 0666) = 3
fcntl(1, F_GETFD) = 0
fcntl(1, F_DUPFD, 10) = 10
fcntl(1, F_GETFD) = 0
fcntl(10, F_SETFD, FD_CLOEXEC) = 0
dup2(3, 1) = 1
close(3) = 0
write(1, "1000\n", 5) = 5
dup2(10, 1) = 1
fcntl(10, F_GETFD) = 0x1 (flags FD_CLOEXEC)
close(10) = 0
そしてexec 3>&1 1>x
私たちはもっときれいになります
write(1, "995\n", 4) = 4
write(1, "996\n", 4) = 4
write(1, "997\n", 4) = 4
write(1, "998\n", 4) = 4
write(1, "999\n", 4) = 4
write(1, "1000\n", 5) = 5
ただし、違いは、「FDを使用する」からではなく、リダイレクトを実行する場所によるものです。たとえば、これにより、for i in {1..1000}; do echo "$i"; done > x
2番目の例とほぼ同じパフォーマンスが得られます。
bash s3.sh 10,35s user 3,70s system 100% cpu 14,042 total
答え3
このスレッドにいくつかの新しい情報をまとめて追加するために、効率に基づいてソートされた4つの方法を比較しました。 2つのテストシリーズに基づいて、時間測定(ユーザー+システム)で100万回の繰り返しの効率を推定します。
- これら2つはほぼ同じです。
- 単純
>
ループリダイレクト(時間:100%) exec
フルループは一度使用されます(時間:~100%)
- 単純
- 各反復について
>>
(時間:200% - 250%) - 各反復について
exec
(時間:340% - 480%)
結論はこうです。
一つある小さいexec
単純なリダイレクトと一緒に使用します(例>>
:.(単純な方が安い)。単一のコマンド実行レベルでは表示されませんが、反復回数が増加するにつれて違いが目に付きます。わかるように、違い。