โปรแกรมบรรทัดคำสั่งสามารถป้องกันไม่ให้มีการเปลี่ยนเส้นทางเอาต์พุตได้หรือไม่?


49

ฉันคุ้นเคยกับการทำสิ่งนี้: someprogram >output.file

ฉันทำเมื่อใดก็ตามที่ฉันต้องการบันทึกผลลัพธ์ที่โปรแกรมสร้างลงในไฟล์ ฉันยังรับรู้ถึงสองตัวแปรของการเปลี่ยนเส้นทาง IOนี้:

  • someprogram 2>output.of.stderr.file (สำหรับ stderr)
  • someprogram &>output.stderr.and.stdout.file (สำหรับทั้ง stdout + stderr รวมกัน)

วันนี้ฉันได้วิ่งข้ามสถานการณ์ที่ฉันไม่คิดว่าเป็นไปได้ ฉันใช้คำสั่งต่อไปนี้xinput test 10และตามที่คาดหวังฉันมีผลลัพธ์ต่อไปนี้:

user @ hostname: ~ $ xinput ทดสอบ 10
กดปุ่ม 30 
การปลดล็อคกุญแจ 30 
กดปุ่ม 40 
การปลดล็อคกุญแจ 40 
กดปุ่ม 32 
การปลดล็อคกุญแจ 32 
กดปุ่ม 65 
การปลดล็อคกุญแจ 65 
กดปุ่ม 61 
ปล่อยกุญแจ 61 
กดปุ่ม 31 
^ C
ผู้ใช้ @ ชื่อโฮสต์: ~ $ 

ฉันคาดว่าเอาต์พุตนี้จะสามารถบันทึกเป็นไฟล์แบบxinput test 10 > output.fileปกติได้ แต่เมื่อขัดกับความคาดหวังของฉันไฟล์ output.file ยังคงว่างเปล่า นี่ก็เป็นจริงxinput test 10 &> output.fileเช่นกันเพื่อให้แน่ใจว่าฉันจะไม่พลาดบางสิ่งบางอย่างใน stdout หรือ stderr

ฉันสับสนจริงๆและด้วยเหตุนี้จึงถามที่นี่ว่าxinputโปรแกรมอาจมีวิธีหลีกเลี่ยงการเปลี่ยนเส้นทางผลลัพธ์หรือไม่

ปรับปรุง

ฉันได้ดูที่แหล่งที่มา ดูเหมือนว่าผลลัพธ์จะถูกสร้างโดยรหัสนี้ (ดูตัวอย่างด้านล่าง) ดูเหมือนว่าฉันจะสร้างผลลัพธ์โดย printf สามัญ

// ในไฟล์ test.c

โมฆะคงที่ print_events (Display * dpy)
{
    เหตุการณ์ XEvent;

    ในขณะที่ (1) {
    XNextEvent (dpy, & Event);

    // [... มีการละเว้นประเภทกิจกรรมอื่น ๆ ที่นี่ ... ]

        if ((Event.type == key_press_type) ||
           (Event.type == key_release_type)) {
        int ลูป;
        XDeviceKeyEvent * key = (XDeviceKeyEvent *) & กิจกรรม;

        printf ("key% s% d", (Event.type == key_release_type)? "release": "กด", key-> keycode);

        สำหรับ (loop = 0; loopaxes_count; loop ++) {
        printf ("a [% d] =% d", key-> first_axis + loop, key-> axis_data [loop]);
        }
        printf ( "\ n");
    } 
    }
}

ฉันแก้ไขซอร์สโค้ดเป็นแบบนี้ (ดูตัวอย่างถัดไปด้านล่าง) ซึ่งอนุญาตให้ฉันมีสำเนาของเอาต์พุตบน stderr ผลลัพธ์นี้ฉันสามารถเปลี่ยนเส้นทางได้:

 // ในไฟล์ test.c

โมฆะคงที่ print_events (Display * dpy)
{
    เหตุการณ์ XEvent;

    ในขณะที่ (1) {
    XNextEvent (dpy, & Event);

    // [... มีการละเว้นประเภทกิจกรรมอื่น ๆ ที่นี่ ... ]

        if ((Event.type == key_press_type) ||
           (Event.type == key_release_type)) {
        int ลูป;
        XDeviceKeyEvent * key = (XDeviceKeyEvent *) & กิจกรรม;

        printf ("key% s% d", (Event.type == key_release_type)? "release": "กด", key-> keycode);
        fprintf (stderr, "key% s% d", (Event.type == key_release_type)? "release": "กด", key-> keycode>;

        สำหรับ (loop = 0; loopaxes_count; loop ++) {
        printf ("a [% d] =% d", key-> first_axis + loop, key-> axis_data [loop]);
        }
        printf ( "\ n");
    } 
    }
}

ความคิดของฉันในปัจจุบันคือการเปลี่ยนเส้นทางโปรแกรมอาจสูญเสียความสามารถในการตรวจสอบกิจกรรมการกดปุ่มกด

คำตอบ:


55

เป็นเพียงว่าเมื่อ stdout ไม่ใช่เทอร์มินัลเอาต์พุตจะถูกบัฟเฟอร์

และเมื่อคุณกดCtrl-Cบัฟเฟอร์นั้นจะหายไปตาม / ถ้ามันยังไม่ถูกเขียน

stdioคุณจะได้รับพฤติกรรมเดียวกันกับสิ่งที่ใช้ ลองตัวอย่างเช่น:

grep . > file

ป้อนบรรทัดที่ไม่ว่างเปล่าสองสามบรรทัดแล้วกดCtrl-Cและคุณจะเห็นไฟล์ว่างเปล่า

ในทางกลับกันให้พิมพ์:

xinput test 10 > file

และพิมพ์พอบนแป้นพิมพ์เพื่อให้บัฟเฟอร์ได้รับเต็ม (อย่างน้อย 4k มูลค่า ouput) และคุณจะเห็นขนาดของไฟล์ที่เติบโตโดยชิ้นของ 4k ในเวลา

ด้วยgrepคุณสามารถพิมพ์Ctrl-Dสำหรับgrepเพื่อออกได้อย่างสง่างามหลังจากล้างกันชน สำหรับxinputฉันไม่คิดว่าจะมีตัวเลือกดังกล่าว

โปรดทราบว่าโดยค่าเริ่มต้นstderrจะไม่ถูกบัฟเฟอร์ซึ่งอธิบายว่าทำไมคุณถึงมีพฤติกรรมที่แตกต่างfprintf(stderr)

หากในxinput.cคุณเพิ่ม a signal(SIGINT, exit)ที่บอกxinputให้ออกอย่างสง่างามเมื่อได้รับSIGINTคุณจะเห็นว่าfileไม่มีอีกต่อไป (สมมติว่ามันไม่ทำงานผิดพลาดเนื่องจากการเรียกฟังก์ชันไลบรารีจากตัวจัดการสัญญาณไม่รับประกันความปลอดภัย: พิจารณาสิ่งที่ อาจเกิดขึ้นได้หากสัญญาณเข้ามาในขณะที่ printf กำลังเขียนไปยังบัฟเฟอร์)

หากพร้อมใช้งานคุณสามารถใช้stdbufคำสั่งเพื่อเปลี่ยนstdioลักษณะการบัฟเฟอร์:

stdbuf -oL xinput test 10 > file

มีคำถามมากมายในไซต์นี้ที่ครอบคลุมการปิดใช้งานการบัฟเฟอร์ประเภทstdioซึ่งคุณจะพบโซลูชันทางเลือกมากยิ่งขึ้น


2
ว้าว :) นั่นเป็นกลอุบาย ขอขอบคุณ. ดังนั้นในที่สุดการรับรู้ของฉันของปัญหาก็ผิด ไม่มีสิ่งใดที่จะยับยั้งการเปลี่ยนเส้นทางมันเป็นเรื่องง่าย Ctrl-C หยุดมันก่อนที่ข้อมูลจะถูกล้าง ขอบคุณ
humanityANDpeace

จะมีวิธีในการป้องกันการบัฟเฟอร์ของ stdout หรือไม่?
HumanityANDpeace

1
@ Stephane Chazelas: ขอบคุณมากสำหรับคำอธิบายโดยละเอียดของคุณ นอกจากสิ่งที่คุณพูดไปแล้วฉันพบว่ามีใครสามารถตั้งค่าบัฟเฟอร์ให้เป็นบัฟเฟอร์setvbuf(stdout, (char *) NULL, _IONBF, NULL)ได้ บางทีนี่อาจเป็นเรื่องที่น่าสนใจด้วย!
user1146332

4
@ user1146332 ใช่นั่นจะเป็นสิ่งที่stdbuf -o0ทำในขณะที่stdbug -oLคืนค่าการบัฟเฟอร์บรรทัดเช่นเมื่อเอาต์พุตไปยังเทอร์มินัล stdbufบังคับให้แอปพลิเคชันโทรหาsetvbufโดยใช้LD_PRELOADกลลวง
Stéphane Chazelas

วิธีแก้ปัญหาอื่น: unbuffer test 10 > file( unbufferเป็นส่วนหนึ่งของexpectเครื่องมือ)
Olivier Dulac

23

คำสั่งสามารถเขียนโดยตรงเพื่อ/dev/ttyป้องกันการเปลี่ยนเส้นทางปกติที่จะเกิดขึ้น

$ 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

ตัวอย่างของคุณทำให้จุด + ตอบคำถาม ใช่มันเป็นไปได้ แน่นอนว่า "ไม่คาดคิด" และเป็นเรื่องผิดปกติสำหรับโปรแกรมที่จะทำเช่นนั้นซึ่งอย่างน้อยก็หลอกฉันในการไม่พิจารณาสิ่งที่เป็นไปได้ คำตอบของผู้ใช้ 1146332 นั้นดูเหมือนจะเป็นวิธีที่น่าเชื่อถือในการหลีกเลี่ยงการเปลี่ยนเส้นทาง เพื่อความยุติธรรมและเนื่องจากทั้งสองคำตอบที่ได้รับนั้นมีความเป็นไปได้ที่เท่าเทียมกันในการหลีกเลี่ยงการเปลี่ยนเส้นทางของผลลัพธ์ของโปรแกรมบรรทัดคำสั่งไปยังไฟล์ฉันไม่สามารถเลือกคำตอบใด ๆ ที่ฉันเดาได้ :( ขอบคุณ!
humanityANDpeace

1
FTR ถ้าคุณต้องการบันทึกเอาต์พุตที่เขียน/dev/ttyบนระบบ Linux ให้ใช้script -c ./demo demo.log(จากutil-linux)
ndim

หากคุณไม่ได้ทำงานใน tty แต่ใช้ pty แทนคุณสามารถค้นหาได้โดยดูที่ procfs (/ proc / $ PID / fd / 0 ฯลฯ ) หากต้องการเขียนไปยัง pty ที่เหมาะสมไปที่ไดเรกทอรี fd ของโปรเซสของคุณและดูว่ามันเป็น symlink ไปที่ / dev / pts / [0-9] + จากนั้นคุณเขียนไปยังอุปกรณ์นั้น (หรือเรียกคืนหากไม่ใช่ pts)
dhasenan

9

ดูเหมือนว่าxinputปฏิเสธการส่งออกไปยังไฟล์ แต่ไม่ได้ปฏิเสธการส่งออกไปยังสถานี เพื่อให้บรรลุเป้าหมายนี้อาจxinputใช้การเรียกของระบบ

int isatty(int fd)

เพื่อตรวจสอบว่าเปิดไฟล์ที่อ้างอิงนั้นหมายถึงเทอร์มินัลหรือไม่

ฉัน stumbled dpicเมื่อปรากฏการณ์เดียวกันในขณะที่ที่ผ่านมากับโปรแกรมที่เรียกว่า หลังจากที่ฉันดูแหล่งที่มาและการดีบั๊กฉันลบบรรทัดที่เกี่ยวข้องisattyและทุกอย่างทำงานตามที่คาดไว้อีกครั้ง

แต่ฉันเห็นด้วยกับคุณว่าประสบการณ์นี้รบกวนมาก;)


ฉันคิดว่าฉันมีการอธิบายของฉัน แต่ (1) ดูซอร์สโค้ด (ไฟล์ test.c ในแพ็กเกจ xinput) ไม่มีisattyการทดสอบปรากฏขึ้น ouput ถูกสร้างขึ้นโดยprintfฟังก์ชั่น (ฉันคิดว่ามันเป็นมาตรฐาน C) อย่างใดอย่างหนึ่ง ฉันเพิ่มบางส่วนfprintf(stderr,"output")และนี่เป็นไปได้ที่จะเปลี่ยนเส้นทาง + พิสูจน์รหัสทั้งหมดจะถูก runned จริงๆในกรณีของ xinput ขอบคุณสำหรับคำแนะนำหลังจากทั้งหมดเป็นเส้นทางแรกที่นี่
HumanityANDpeace

0

ในtest.cไฟล์ของคุณคุณสามารถล้างข้อมูลบัฟเฟอร์ที่ใช้(void)fflush(stdout);โดยตรงหลังจากprintfคำสั่งของคุณ

    // 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);

บนบรรทัดคำสั่งคุณสามารถเปิดใช้งานเอาต์พุตที่บัฟเฟอร์บรรทัดโดยการรันxinput test 10ในเทอร์มินัลหลอก (pty) ด้วยscriptคำสั่ง

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

-1

ใช่. ฉันยังทำสิ่งนี้ใน DOS ครั้งเมื่อฉันตั้งโปรแกรมในปาสกาล ฉันเดาว่าหลักการยังคงมีอยู่:

  1. ปิด stdout
  2. เปิด stdout ใหม่เป็นคอนโซล
  3. เขียนเอาต์พุตไปยัง stdout

นี่ทำให้ท่อแตกได้


“ Re-Open stdout”: stdout ถูกกำหนดเป็น file descriptor 1 คุณสามารถเปิด file descriptor 1 อีกครั้ง แต่คุณจะเปิดไฟล์อะไร คุณอาจจะหมายถึงการเปิด terminal, ในกรณีที่มันไม่สำคัญว่าโปรแกรมที่ถูกเขียนถึง fd 1.
กิลส์ 'ทีเราหยุดเป็นคนชั่ว'

@Gilles ไฟล์คือ "con:" เท่าที่ฉันจำได้ - แต่ใช่ฉันกลั่นกรองจุดที่ 2 ในทิศทางนั้น
นิลส์

conคือชื่อ DOS สำหรับสิ่งที่เรียกยูนิกซ์/dev/ttyเช่นเทอร์มินัล (การควบคุม)
Gilles 'หยุดความชั่วร้าย'
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.