Một chương trình dòng lệnh có thể ngăn chặn đầu ra của nó được chuyển hướng?


49

Tôi đã trở nên quá quen với việc này: someprogram >output.file

Tôi làm điều đó bất cứ khi nào tôi muốn lưu kết quả mà chương trình tạo ra vào một tệp. Tôi cũng nhận thức được hai biến thể của chuyển hướng IO này :

  • someprogram 2>output.of.stderr.file (đối với thiết bị lỗi thời)
  • someprogram &>output.stderr.and.stdout.file (cho cả stdout + stderr kết hợp)

Hôm nay tôi đã chạy qua một tình huống mà tôi không nghĩ là có thể. Tôi sử dụng lệnh sau xinput test 10và như mong đợi, tôi có đầu ra sau:

tên người dùng @ tên máy chủ: ~ $ xinput test 10
nhấn phím 30 
phát hành khóa 30 
nhấn phím 40 
phát hành khóa 40 
nhấn phím 32 
phát hành khóa 32 
nhấn phím 65 
phát hành khóa 65 
nhấn phím 61 
phát hành khóa 61 
nhấn phím 31 
^ C
tên người dùng @ tên: ~ $ 

Tôi hy vọng rằng đầu ra này có thể được lưu vào một tập tin như sử dụng xinput test 10 > output.file. Nhưng khi trái với dự đoán của tôi, tệp output.file vẫn trống. Điều này cũng đúng vì xinput test 10 &> output.filechỉ để đảm bảo rằng tôi không bỏ lỡ điều gì trên thiết bị xuất chuẩn hoặc thiết bị xuất chuẩn.

Tôi thực sự bối rối và do đó hỏi ở đây liệu xinputchương trình có thể có cách nào để tránh đầu ra của nó bị chuyển hướng không?

cập nhật

Tôi đã xem nguồn. Có vẻ như đầu ra được tạo bởi mã này (xem đoạn trích bên dưới). Nó xuất hiện với tôi đầu ra sẽ được tạo ra bởi một printf thông thường

// trong tệp test.c

void void print_events (Hiển thị * dpy)
{
    Sự kiện XEvent;

    trong khi (1) {
    XNextEvent (dpy, & Sự kiện);

    // [... một số loại sự kiện khác được bỏ qua ở đây ...]

        if ((Event.type == key_press_type) ||
           (Event.type == key_release_type)) {
        vòng lặp int;
        XDeviceKeyEvent * key = (XDeviceKeyEvent *) & Sự kiện;

        printf ("key% s% d", (Event.type == key_release_type)? "phát hành": "nhấn", key-> keycode);

        for (loop = 0; loopaxes_count; loop ++) {
        printf ("a [% d] =% d", key-> first_axis + loop, key-> ax_data [loop]);
        }
        printf ("\ n");
    } 
    }
}

Tôi đã sửa đổi nguồn này (xem đoạn trích tiếp theo bên dưới), cho phép tôi có một bản sao của đầu ra trên stderr. Đầu ra này tôi có thể chuyển hướng:

 // trong tệp test.c

void void print_events (Hiển thị * dpy)
{
    Sự kiện XEvent;

    trong khi (1) {
    XNextEvent (dpy, & Sự kiện);

    // [... một số loại sự kiện khác được bỏ qua ở đây ...]

        if ((Event.type == key_press_type) ||
           (Event.type == key_release_type)) {
        vòng lặp int;
        XDeviceKeyEvent * key = (XDeviceKeyEvent *) & Sự kiện;

        printf ("key% s% d", (Event.type == key_release_type)? "phát hành": "nhấn", key-> keycode);
        fprintf (stderr, "key% s% d", (Event.type == key_release_type)? "phát hành": "nhấn", key-> keycode);

        for (loop = 0; loopaxes_count; loop ++) {
        printf ("a [% d] =% d", key-> first_axis + loop, key-> ax_data [loop]);
        }
        printf ("\ n");
    } 
    }
}

Ý tưởng của tôi hiện tại là có thể bằng cách thực hiện chuyển hướng, chương trình mất khả năng giám sát các sự kiện phát hành phím bấm phím.

Câu trả lời:


55

Chỉ là khi thiết bị xuất chuẩn không phải là thiết bị đầu cuối, đầu ra được đệm.

Và khi bạn nhấn Ctrl-C, bộ đệm đó sẽ bị mất như / nếu nó chưa được ghi.

Bạn có được hành vi tương tự với bất cứ điều gì bằng cách sử dụng stdio. Thử ví dụ:

grep . > file

Nhập một vài dòng không trống và nhấn Ctrl-Cvà bạn sẽ thấy tệp trống.

Mặt khác, gõ:

xinput test 10 > file

Và nhập đủ trên bàn phím để bộ đệm được đầy đủ (ít nhất 4k trị giá), và bạn sẽ thấy kích thước tệp tăng lên theo khối 4k mỗi lần.

Với grep, bạn có thể gõ Ctrl-Dcho grepđể thoát một cách duyên dáng sau khi đã đỏ mặt đệm. Đối với xinput, tôi không nghĩ có một lựa chọn như vậy.

Lưu ý rằng theo mặc định stderrkhông được đệm, điều này giải thích tại sao bạn có hành vi khác vớifprintf(stderr)

Nếu, trong xinput.c, bạn thêm một signal(SIGINT, exit), nghĩa là xinputthoát ra một cách duyên dáng khi nhận được SIGINT, bạn sẽ thấy filenó không còn trống nữa (giả sử nó không gặp sự cố, vì việc gọi các chức năng thư viện từ trình xử lý tín hiệu không được đảm bảo an toàn: hãy xem xét những gì có thể xảy ra nếu tín hiệu đến trong khi printf đang ghi vào bộ đệm).

Nếu nó khả dụng, bạn có thể sử dụng stdbuflệnh để thay đổi stdiohành vi đệm:

stdbuf -oL xinput test 10 > file

Có rất nhiều câu hỏi trên trang web này bao gồm việc vô hiệu hóa bộ đệm loại stdio nơi bạn sẽ tìm thấy nhiều giải pháp thay thế hơn.


2
WOW :) đã làm điều đó. cảm ơn bạn. Vì vậy, cuối cùng nhận thức của tôi về vấn đề là sai. Không có gì để ngăn chặn chuyển hướng, thật đơn giản Ctrl-C đã dừng nó trước khi dữ liệu bị xóa. cảm ơn bạn
nhân

Sẽ có một cách để ngăn chặn bộ đệm của thiết bị xuất chuẩn?
nhân

1
@Stephane Chazelas: cảm ơn rất nhiều vì lời giải thích chi tiết của bạn. Ngoài những gì bạn đã nói, tôi phát hiện ra rằng người ta có thể đặt bộ đệm thành không có bộ đệm setvbuf(stdout, (char *) NULL, _IONBF, NULL). Có lẽ đây cũng là điều đáng quan tâm!?
dùng1146332

4
@ user1146332, vâng, đó sẽ là những gì stdbuf -o0, trong khi stdbug -oLkhôi phục bộ đệm dòng như khi đầu ra đi đến một thiết bị đầu cuối. stdbufkhông buộc ứng dụng gọi setvbufbằng LD_PRELOADthủ thuật.
Stéphane Chazelas

một workaroudn khác: unbuffer test 10 > file( unbufferlà một phần của các expectcông cụ)
Olivier Dulac

23

Một lệnh có thể trực tiếp viết để /dev/ttyngăn chặn chuyển hướng thường xuyên xảy ra.

$ cat demo
#!/bin/ksh
LC_ALL=C TZ=Z date > /dev/tty
$ ./demo >demo.out 2>demo.err
Fri Dec 28 10:31:57  2012
$ ls -l demo*
-rwxr-xr-x 1 jlliagre jlliagre 41 2012-12-28 11:31 demo
-rw-r--r-- 1 jlliagre jlliagre  0 2012-12-28 11:31 demo.err
-rw-r--r-- 1 jlliagre jlliagre  0 2012-12-28 11:31 demo.out

Ví dụ của bạn làm cho điểm + trả lời câu hỏi. Vâng, nó là có thể. Tất nhiên là "bất ngờ" và không phổ biến cho các chương trình để làm như vậy, mà ít nhất là đánh lừa tôi khi không xem xét một điều như vậy có thể. Câu trả lời của user1146332 cũng có vẻ là một cách thuyết phục để tránh chuyển hướng. Để công bằng và vì cả hai câu trả lời được đưa ra đều là những cách có thể như nhau để tránh chuyển hướng đầu ra chương trình dòng lệnh vào một tệp, tôi không thể chọn bất kỳ câu trả lời nào tôi đoán :(. Tôi sẽ cần được phép chọn hai câu trả lời đúng. Cảm ơn bạn!
nhân

1
FTR, nếu bạn muốn nắm bắt đầu ra được ghi /dev/ttytrên hệ thống Linux, hãy sử dụng script -c ./demo demo.log(từ util-linux).
ndim

Nếu bạn không chạy trong một tty, nhưng thay vào đó là một pty, bạn có thể tìm thấy điều đó bằng cách xem Procfs (/ Proc / $ PID / fd / 0, v.v.). Để viết thư cho pty thích hợp, hãy chuyển đến thư mục fd của tiến trình cha mẹ của bạn và xem liệu đó có phải là một liên kết tượng trưng đến / dev / pts / [0-9] +. Sau đó, bạn viết thư cho thiết bị đó (hoặc lặp lại nếu đó không phải là điểm).
dhasenan

9

Có vẻ như xinputtừ chối đầu ra cho một tệp nhưng không từ chối đầu ra đến một thiết bị đầu cuối. Để đạt được điều này, có thể xinputsử dụng cuộc gọi hệ thống

int isatty(int fd)

để kiểm tra xem filedescriptor sẽ được mở có tham chiếu đến một thiết bị đầu cuối hay không.

Tôi tình cờ thấy hiện tượng tương tự một thời gian trước với một chương trình được gọi là dpic. Sau khi tôi xem xét nguồn và một số gỡ lỗi, tôi đã xóa các dòng liên quan đến isattyvà mọi thứ hoạt động như mong đợi một lần nữa.

Nhưng tôi đồng ý với bạn rằng trải nghiệm này rất đáng lo ngại;)


Tôi thực sự nghĩ rằng tôi đã khám phá của tôi. Nhưng (1) nhìn vào nguồn (tệp test.c trong gói nguồn xinput) không có sự xuất hiện của isattythử nghiệm được thực hiện. Các ouput được tạo bởi printfchức năng (tôi nghĩ đó là một tiêu chuẩn C). Tôi đã thêm một số fprintf(stderr,"output")và điều này có thể chuyển hướng + chứng minh toàn bộ mã thực sự được chạy trong trường hợp xinput. Cảm ơn bạn đã gợi ý sau tất cả, đó là con đường đầu tiên ở đây.
nhân

0

Trong test.ctệp của bạn, bạn có thể xóa dữ liệu được đệm bằng cách sử dụng (void)fflush(stdout);trực tiếp sau printfcâu lệnh của mình .

    // in test.c
    printf("key %s %d ", (Event.type == key_release_type) ? "release" : "press  ", key->keycode);
    //fprintf(stderr,"key %s %d ", (Event.type == key_release_type) ? "release" : "press  ", key->keycode);
    //(void)fflush(NULL);
    (void)fflush(stdout);

Trên dòng lệnh, bạn có thể kích hoạt đầu ra đệm dòng bằng cách chạy xinput test 10trong thiết bị đầu cuối giả (pty) bằng scriptlệnh.

script -q /dev/null xinput test 10 > file      # FreeBSD, Mac OS X
script -c "xinput test 10" /dev/null > file    # Linux

-1

Đúng. Tôi thậm chí đã làm điều này trong DOS - khi tôi lập trình bằng pascal. Tôi đoán nguyên tắc vẫn giữ:

  1. Đóng thiết bị xuất chuẩn
  2. Mở lại thiết bị xuất chuẩn như bảng điều khiển
  3. Viết đầu ra cho thiết bị xuất chuẩn

Điều này đã phá vỡ bất kỳ đường ống.


Phần mở rộng lại của Re -out Stoutout: stdout được định nghĩa là bộ mô tả tập tin 1. Bạn có thể mở lại bộ mô tả tập tin 1, nhưng bạn sẽ mở tập tin nào? Bạn có thể có nghĩa là mở thiết bị đầu cuối, trong trường hợp đó không quan trọng liệu chương trình có được viết cho fd 1.
Gilles 'SO- ngừng trở thành ác quỷ'

@Gilles tệp là "con:" theo như tôi nhớ - nhưng vâng, tôi đã tinh chỉnh điểm 2 theo hướng đó.
Nils

conlà tên DOS cho những gì unix gọi /dev/tty, tức là thiết bị đầu cuối (kiểm soát).
Gilles 'SO- ngừng trở nên xấu xa'
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.