Apakah mungkin untuk mengakses baris perintah lengkap termasuk pipa dalam skrip bash?

8

Misal, baris perintah:

test.sh arg1 | grep "xyz"

Apakah mungkin untuk mendapatkan baris perintah yang lengkap termasuk grep berikut dalam bash script test.sh?

hellcode
sumber
dapatkah Anda menjelaskan apa yang Anda maksud dengan "baris perintah"?
Bart
Saya hanya ingin tahu apakah ada variabel dolar khusus yang berisi string lengkap (baris perintah), bukan hanya nama skrip dan argumennya
hellcode
2
Apa yang akan Anda gunakan untuk melakukan hal ini?
Kusalananda
9
@ kode sandi Anda tidak perlu tahu apakah Anda berada di dalam pipa untuk itu. Periksa apakah output adalah TTY. [ -t 1 ] unix.stackexchange.com/a/401938/70524
muru
1
Berkaitan dengan: unix.stackexchange.com/q/485271/117549
Jeff Schaller

Jawaban:

6

Tidak ada cara untuk melakukan itu secara umum .

Tetapi bashshell interaktif dapat memanfaatkan mekanisme histori dan DEBUGjebakan untuk "memberi tahu" perintah yang menjalankan baris perintah lengkap yang menjadi bagian mereka melalui variabel lingkungan:

$ trap 'export LC=$(fc -nl -0); LC=${LC#? }' DEBUG
$ sh -c 'printf "last_command={%s}\n" "$LC"' | cat; true
last_command={sh -c 'printf "last_command={%s}\n" "$LC"' | cat; true}
mosvy
sumber
13

tidak

bash (atau shell Anda) akan melakukan dua perintah berbeda.

  1. test.sh arg1
  2. grep "xyz"

test.sh tidak bisa tahu tentang mengikuti grep.

Namun Anda mungkin tahu Anda "di dalam" pipa dengan pengujian /proc/self/fd/1

test.sh

#!/bin/bash

file /proc/self/fd/1

yang dijalankan sebagai

> ./test.sh
/proc/self/fd/1: symbolic link to /dev/pts/0
> ./test.sh | cat
/proc/self/fd/1: broken symbolic link to pipe:[25544239]

(Sunting) lihat komentar muru tentang mengetahui apakah Anda menggunakan pipa.

Anda tidak perlu tahu apakah Anda berada di dalam pipa untuk itu. Periksa apakah output adalah TTY. [ -t 1 ] https://unix.stackexchange.com/a/401938/70524

Archemar
sumber
Meskipun bermanfaat, ini hanya berfungsi di Linux - bukan Unix lain
Scott Earle
2

Dengan menggunakan /proc/self/fd, Anda dapat melihat apakah Anda berada di jalur pipa serta ID untuk pipa tersebut. Jika Anda beralih melalui /proc/\*/fdmencari pipa yang cocok, Anda dapat menemukan PID dari ujung pipa yang lain. Dengan PID, Anda dapat membaca /proc/$PID/cmdlineserta mengulangi proses pada deskriptor file untuk menemukan apa yang disalurkan.

$ cat | cat | cat &
$ ps
  PID TTY          TIME CMD
 6942 pts/16   00:00:00 cat
 6943 pts/16   00:00:00 cat
 6944 pts/16   00:00:00 cat
 7201 pts/16   00:00:00 ps
20925 pts/16   00:00:00 bash
$ ls -l /proc/6942/fd
lrwx------. 1 tim tim 64 Jul 24 19:59 0 -> /dev/pts/16
l-wx------. 1 tim tim 64 Jul 24 19:59 1 -> 'pipe:[49581130]'
lrwx------. 1 tim tim 64 Jul 24 19:59 2 -> /dev/pts/16
$ ls -l /proc/6943/fd
lr-x------. 1 tim tim 64 Jul 24 19:59 0 -> 'pipe:[49581130]'
l-wx------. 1 tim tim 64 Jul 24 19:59 1 -> 'pipe:[49581132]'
lrwx------. 1 tim tim 64 Jul 24 19:59 2 -> /dev/pts/16
$ ls -l /proc/6944/fd
lr-x------. 1 tim tim 64 Jul 24 19:59 0 -> 'pipe:[49581132]'
lrwx------. 1 tim tim 64 Jul 24 19:59 1 -> /dev/pts/16
lrwx------. 1 tim tim 64 Jul 24 19:59 2 -> /dev/pts/16

Juga jika Anda beruntung, perintah yang berbeda dalam pipa akan mendapatkan PID berurutan yang akan membuatnya sedikit lebih mudah.

Saya sebenarnya tidak memiliki skrip untuk melakukan ini, tetapi saya telah membuktikan konsepnya.

Tim Anderson
sumber
1

Cara lain mungkin dengan mengakses $BASH_COMMANDvariabel otomatis, tetapi secara inheren volatile dan sulit untuk menangkap nilai yang diinginkan.

Saya pikir Anda hanya bisa menangkapnya hanya melalui eval, yang juga melibatkan pemanggilan perintah Anda dengan cara khusus, seperti di:

CMD="${BASH_COMMAND##* eval }" eval './test.sh arg1 | grep "xyz"'

Di sini $BASH_COMMANDdiperluas sementara juga membersihkannya hingga evalsedikit string, dan string hasil dengan demikian "snapshotted" menjadi $CMDvariabel pembantu .

Sedikit contoh:

$ cat test.sh
#!/bin/sh

printf 'you are running %s\n' "$CMD"
sleep 1
echo bye bye
$
$ CMD="${BASH_COMMAND##* eval }" eval './test.sh | { grep -nH "."; }'
(standard input):1:you are running './test.sh | { grep -nH "."; }'
(standard input):2:bye bye
$

Secara alami itu juga bisa bekerja (sebenarnya lebih baik) sambil memohon skrip dengan misalnya sh -catau bash -c, seperti pada:

$
$ CMD="${BASH_COMMAND}" sh -c './test.sh | { grep -nH "."; }'
(standard input):1:you are running CMD="${BASH_COMMAND}" sh -c './test.sh | { grep -nH "."; }'
(standard input):2:bye bye
$

Di sini tanpa membersihkan variabel.

LL3
sumber
1

Terima kasih atas jawaban anda Saya telah menguji berbagai hal dan membuka skrip pengujian berikut:

test.sh:

hist=`fc -nl -0`
# remove leading and trailing whitespaces
hist="$(echo "${hist}" | sed -e 's/^[[:space:]]*//' -e 's/[[:space:]]*$//')"
echo "Command line from history: '$hist'"

if [ -t 1 ]; then
  echo "Direct output to TTY, no pipe involved."
else
  echo "No TTY, maybe a piped command."
fi

if [ -p /dev/stdout ]; then
  echo "stdout is a pipe."
else
  echo "stdout is not a pipe."
fi

readlink -e /proc/self/fd/1
rst=$?
if [ $rst -eq 0 ]; then
  echo "Readlink test status okay, no pipe involved."
else
  echo "Readlink test status error $rst, maybe a piped command."
fi

Tes:

$ ./test.sh test1
Command line from history: './test.sh test1'
Direct output to TTY, no pipe involved.
stdout is not a pipe.
/dev/pts/3
Readlink test status okay, no pipe involved.

$ ./test.sh test2 | cat
Command line from history: './test.sh test2 | cat'
No TTY, maybe a piped command.
stdout is a pipe.
Readlink test status error 1, maybe a piped command.

$ echo "another command before pipe doesn't matter" | ./test.sh test3
Command line from history: 'echo "another command before pipe doesn't matter" | ./test.sh test3'
Direct output to TTY, no pipe involved.
stdout is not a pipe.
/dev/pts/3
Readlink test status okay, no pipe involved.

Riwayat baris perintah hanya berfungsi tanpa Shebang di baris teratas skrip. Tidak tahu apakah ini akan bekerja andal dan pada sistem lain juga.

Saya tidak dapat menekan output dari "readlink" (atau "file" seperti yang disarankan dari Archemar), ketika statusnya berhasil ("/ dev / pts / 3"). Output perpipaan ke / dev / null atau ke suatu variabel akan menyebabkan kegagalan fungsi. Jadi ini bukan pilihan bagi saya dalam naskah.

Pemeriksaan TTY bahwa muru disebutkan mudah dan mungkin sudah cukup untuk beberapa kasus penggunaan.

Sunting: Kredit saya jatuh ke mosvy, karena pertanyaannya adalah bagaimana cara mendapatkan baris perintah yang lengkap dan bukan hanya untuk menentukan apakah skrip ada di pipa. Saya suka bagian sederhana "fc -nl -0" dalam jawabannya, karena tidak ada konfigurasi sistem lebih lanjut yang diperlukan. Ini bukan solusi 100 persen, tetapi ini hanya untuk penggunaan pribadi saya dan karena itu cukup. Terima kasih untuk semua yang lain atas bantuan Anda.

hellcode
sumber
Cek TTY juga dapat dilakukan untuk stdin: [ -t 0 ]. Jadi Anda dapat memeriksa apakah stdin atau stdout bukan TTY dan lanjutkan.
muru
Jika Anda ingin tahu apakah stdout adalah sebuah pipa, di Linux Anda dapat menggunakan if [ -p /dev/stdout ]; ...(seperti readlink /proc/self/fd/..ini tidak berfungsi pada BSD).
Mosvy
2
Script perlu bekerja IMNSHO. yang echo -ehampir pasti tidak ingin -e. Anda perlu lebih banyak kasus uji, mengarahkan ke file, dipanggil di dalam $(...). Namun saya ingin Anda mempertimbangkan apakah ini ide yang bagus. Program seperti lsyang mengubah output mereka tergantung pada apakah mereka mengeluarkan ke tty atau pipa yang mengganggu untuk digunakan.
icarus