Cara mengelabui aplikasi agar berpikir stdout-nya adalah terminal, bukan pipa

147

Saya mencoba melakukan kebalikan dari " Deteksi jika stdin adalah terminal atau pipa? ".

Saya menjalankan aplikasi yang mengubah format outputnya karena mendeteksi pipa pada STDOUT, dan saya ingin itu berpikir bahwa ini adalah terminal interaktif sehingga saya mendapatkan output yang sama ketika mengarahkan ulang.

Saya berpikir bahwa membungkusnya dalam expectskrip atau menggunakan proc_open()dalam PHP akan melakukannya, tetapi tidak.

Ada ide di luar sana?

Manusia Timah
sumber
8
Apakah empty.sf.net membantu?
ephemient
1
@ephemient: seharusnya menjadi jawaban. Penggunaan hebat ...
neuro
Pertanyaannya berbicara tentang stdout tetapi judulnya menyebutkan stdin. Saya pikir judulnya salah.
Piotr Dobrogost

Jawaban:

177

Aha!

The scriptperintah melakukan apa yang kita inginkan ...

script --return --quiet -c "[executable string]" /dev/null

Apakah caranya!

Usage:
 script [options] [file]

Make a typescript of a terminal session.

Options:
 -a, --append                  append the output
 -c, --command <command>       run command rather than interactive shell
 -e, --return                  return exit code of the child process
 -f, --flush                   run flush after each write
     --force                   use output file even when it is a link
 -q, --quiet                   be quiet
 -t[<file>], --timing[=<file>] output timing data to stderr or to FILE
 -h, --help                    display this help
 -V, --version                 display version
Christopher Oezbek
sumber
1
+1: tersandung masalah dengan lib yang melakukan inisialisasi statis. Perubahan baru-baru ini di Fedora 12 telah membuat init itu gagal ketika exe yang dipasangkan tidak dalam tty. Trik Anda bekerja dengan sempurna. Saya lebih suka daripada unbuffer karena skrip diinstal secara default!
neuro
scriptbahkan tersedia di BusyBox !
dolmen
9
Jika Anda ingin menyalurkannya ke sesuatu yang interaktif, seperti less -R, ke mana input terminal menuju less -R, maka Anda memerlukan beberapa tipu daya tambahan. Sebagai contoh, saya ingin versi yang berwarna git status | less. Anda harus lulus -Rkurang agar menghormati warna, dan Anda harus menggunakan scriptuntuk mendapatkan git statuswarna keluaran. Tapi kami tidak ingin scriptmempertahankan kepemilikan keyboard, kami ingin ini less. Jadi saya menggunakan ini sekarang dan bekerja dengan baik: 0<&- script -qfc "git status" /dev/null | less -R . Beberapa karakter pertama itu menutup stdin untuk perintah ini.
Aaron McDaid
2
Catatan: Ini tidak berfungsi dalam kasus di mana komponen memeriksa interaktivitas sedang melihat $-variabel shell untuk "i".
Jay Taylor
1
Ini luar biasa. Saya membutuhkan ini untuk kasus penggunaan yang sangat langka dengan pustaka Python tertanam di dalam executable yang dijalankan dalam Wine. Ketika saya berlari di terminal itu berhasil tetapi ketika saya menjalankan file .desktop saya buat itu akan selalu macet karena Py_Initializetidak melihat stdin / stderr yang tepat.
Tatsh
60

Berdasarkan solusi Chris , saya menghasilkan fungsi pembantu kecil berikut:

faketty() {
    script -qfc "$(printf "%q " "$@")" /dev/null
}

Tampak aneh printfdiperlukan untuk memperluas argumen skrip dengan benar $@sambil melindungi bagian-bagian perintah yang mungkin dikutip (lihat contoh di bawah).

Pemakaian:

faketty <command> <args>

Contoh:

$ python -c "import sys; print sys.stdout.isatty()"
True
$ python -c "import sys; print sys.stdout.isatty()" | cat
False
$ faketty python -c "import sys; print sys.stdout.isatty()" | cat
True
ingomueller.net
sumber
9
Anda mungkin ingin menggunakan --returnopsi, jika versi Anda scriptmemilikinya, untuk mempertahankan kode keluar proses anak.
jwd
5
Saya sarankan untuk mengubah fungsi ini seperti ini: function faketty { script -qfc "$(printf "%q " "$@")" /dev/null; } Jika tidak, file bernama typescriptakan dibuat setiap kali perintah dijalankan, dalam banyak kasus.
w0rp
1
sepertinya tidak berfungsi pada MacOS, saya mengerti script: illegal option -- f
Alexander Mills
23

The unbuffer script yang datang dengan Harapkan harus menangani ok ini. Jika tidak, aplikasi mungkin melihat sesuatu selain dari apa outputnya terhubung, misalnya. apa variabel lingkungan TERM diatur ke.

Colin Macleod
sumber
17

Saya tidak tahu apakah itu bisa dilakukan dari PHP, tetapi jika Anda benar-benar membutuhkan proses anak untuk melihat TTY, Anda dapat membuat PTY .

Dalam C:

#include <stdio.h>
#include <stdlib.h>
#include <sysexits.h>
#include <unistd.h>
#include <pty.h>

int main(int argc, char **argv) {
    int master;
    struct winsize win = {
        .ws_col = 80, .ws_row = 24,
        .ws_xpixel = 480, .ws_ypixel = 192,
    };
    pid_t child;

    if (argc < 2) {
        printf("Usage: %s cmd [args...]\n", argv[0]);
        exit(EX_USAGE);
    }

    child = forkpty(&master, NULL, NULL, &win);
    if (child == -1) {
        perror("forkpty failed");
        exit(EX_OSERR);
    }
    if (child == 0) {
        execvp(argv[1], argv + 1);
        perror("exec failed");
        exit(EX_OSERR);
    }

    /* now the child is attached to a real pseudo-TTY instead of a pipe,
     * while the parent can use "master" much like a normal pipe */
}

Saya sebenarnya mendapat kesan bahwa expectitu sendiri memang menciptakan PTY.

singkat
sumber
Apakah Anda tahu cara menjalankan nettop sebagai proses anak di mac os x? Saya ingin mendapatkan output nettop di aplikasi saya. Saya mencoba menggunakan forkpty tetapi masih tidak dapat menjalankan nettop dengan sukses.
Vince Yuan
16

Mengacu jawaban sebelumnya, pada Mac OS X, "skrip" dapat digunakan seperti di bawah ini ...

script -q /dev/null commands...

Tetapi, karena ini dapat menggantikan "\ n" dengan "\ r \ n" di stdout, Anda mungkin juga memerlukan skrip seperti ini:

script -q /dev/null commands... | perl -pe 's/\r\n/\n/g'

Jika ada beberapa pipa di antara perintah-perintah ini, Anda perlu menyiram stdout. sebagai contoh:

script -q /dev/null commands... | ruby -ne 'print "....\n";STDOUT.flush' |  perl -pe 's/\r\n/\n/g'
Tsuneo Yoshioka
sumber
1
Terima kasih atas sintaks OS X, tetapi, dilihat dari pernyataan Perl Anda, sepertinya Anda bermaksud mengatakan bahwa itu mengubah contoh "\ r \ n" menjadi "\ n", bukan sebaliknya, benar?
mklement0
8

Terlalu baru untuk mengomentari jawaban spesifik, tetapi saya pikir saya akan fakettymenindaklanjuti fungsi diposting oleh ingomueller-net di atas karena baru-baru ini membantu saya keluar.

Saya menemukan bahwa ini membuat typescriptfile yang tidak saya inginkan / butuhkan jadi saya menambahkan / dev / null sebagai file target skrip:

function faketty { script -qfc "$(printf "%q " "$@")" /dev/null ; }

A-Ron
sumber
3

Memperbarui @ A-Ron untuk a) bekerja pada Linux & MacOs b) menyebarkan kode status secara tidak langsung (karena MacOs scripttidak mendukungnya)

faketty () {
  # Create a temporary file for storing the status code
  tmp=$(mktemp)

  # Ensure it worked or fail with status 99
  [ "$tmp" ] || return 99

  # Produce a script that runs the command provided to faketty as
  # arguments and stores the status code in the temporary file
  cmd="$(printf '%q ' "$@")"'; echo $? > '$tmp

  # Run the script through /bin/sh with fake tty
  if [ "$(uname)" = "Darwin" ]; then
    # MacOS
    script -Fq /dev/null /bin/sh -c "$cmd"
  else
    script -qfc "/bin/sh -c $(printf "%q " "$cmd")" /dev/null
  fi

  # Ensure that the status code was written to the temporary file or
  # fail with status 99
  [ -s $tmp ] || return 99

  # Collect the status code from the temporary file
  err=$(cat $tmp)

  # Remove the temporary file
  rm -f $tmp

  # Return the status code
  return $err
}

Contoh:

$ faketty false ; echo $?
1

$ faketty echo '$HOME' ; echo $?
$HOME
0

embedded_example () {
  faketty perl -e 'sleep(5); print "Hello  world\n"; exit(3);' > LOGFILE 2>&1 </dev/null &
  pid=$!

  # do something else
  echo 0..
  sleep 2
  echo 2..

  echo wait
  wait $pid
  status=$?
  cat LOGFILE
  echo Exit status: $status
}

$ embedded_example
0..
2..
wait
Hello  world
Exit status: 3
Jonas Berlin
sumber
2

Saya mencoba untuk mendapatkan warna ketika menjalankan shellcheck <file> | less, jadi saya mencoba jawaban di atas, tetapi mereka menghasilkan efek aneh di mana teks secara horizontal diimbangi dari tempat seharusnya:

In ./all/update.sh line 6:
                          for repo in $(cat repos); do
                                                                  ^-- SC2013: To read lines rather than words, pipe/redirect to a 'while read' loop.

(Bagi mereka yang tidak terbiasa dengan shellcheck, garis dengan peringatan itu seharusnya sejalan dengan di mana masalahnya.)

Agar jawaban di atas berfungsi dengan shellcheck, saya mencoba salah satu opsi dari komentar:

faketty() {                       
    0</dev/null script -qfc "$(printf "%q " "$@")" /dev/null
}

Ini bekerja. Saya juga menambahkan --returndan menggunakan opsi panjang, untuk membuat perintah ini sedikit tidak dapat dipahami:

faketty() {                       
    0</dev/null script --quiet --flush --return --command "$(printf "%q " "$@")" /dev/null
}

Bekerja di Bash dan Zsh.

Nick ODell
sumber
1

Ada juga program pty yang termasuk dalam kode contoh buku "Pemrograman Lanjutan di Lingkungan UNIX, Edisi Kedua"!

Berikut cara mengompilasi pty di Mac OS X:

man 4 pty  #  pty -- pseudo terminal driver

open http://en.wikipedia.org/wiki/Pseudo_terminal

# Advanced Programming in the UNIX Environment, Second Edition
open http://www.apuebook.com

cd ~/Desktop

curl -L -O http://www.apuebook.com/src.tar.gz

tar -xzf src.tar.gz

cd apue.2e

wkdir="${HOME}/Desktop/apue.2e"

sed -E -i "" "s|^WKDIR=.*|WKDIR=${wkdir}|" ~/Desktop/apue.2e/Make.defines.macos

echo '#undef _POSIX_C_SOURCE' >> ~/Desktop/apue.2e/include/apue.h

str='#include   <sys/select.h>'
printf '%s\n' H 1i "$str" . wq | ed -s calld/loop.c

str='
#undef _POSIX_C_SOURCE
#include <sys/types.h>
'
printf '%s\n' H 1i "$str" . wq | ed -s file/devrdev.c

str='
#include <sys/signal.h>
#include <sys/ioctl.h>
'
printf '%s\n' H 1i "$str" . wq | ed -s termios/winch.c

make

~/Desktop/apue.2e/pty/pty ls -ld *
jujur
sumber
Kesalahan yang benar-benar aneh juga: Kesalahan cepat: domain tidak dikenal: codesnippets.joyent.com. Harap periksa bahwa domain ini telah ditambahkan ke layanan.
i336_