Cách tốt nhất để dẫn đầu ra của lệnh thông qua máy nhắn tin nếu (và chỉ khi) nó quá dài là gì?


10

Tôi muốn có thể bọc một lệnh để nếu đầu ra của nó không vừa trong một thiết bị đầu cuối, nó sẽ được tự động chuyển qua máy nhắn tin.

Ngay bây giờ tôi đang sử dụng hàm shell sau (trong zsh, trong Arch Linux):

export LESS="-R"

RET="$($@)"
RET_LINES="$(echo "${RET}" | wc -l)"

if [[ $RET_LINES -ge $LINES ]]; then
  echo "${RET}" | ${PAGER:="less"}
else
  echo "${RET}"
fi

nhưng điều này không thực sự thuyết phục tôi. Có cách nào tốt hơn (về sự mạnh mẽ và chi phí) để đạt được những gì tôi muốn không? Tôi cũng mở mã cụ thể zsh, nếu nó hoạt động tốt.


Cập nhật: Vì tôi đã hỏi câu hỏi này, tôi đã tìm thấy một câu trả lời cung cấp một giải pháp tốt hơn - nếu phức tạp hơn -, bộ đệm tại hầu hết $LINEScác dòng trước khi đưa đầu ra vào lessthay vì lưu trữ tất cả. Đáng buồn thay, điều đó cũng không thực sự thỏa mãn, bởi vì cả hai giải pháp đều không có hàng dài. Ví dụ: nếu đoạn mã trên được lưu trữ trong một hàm được gọi pager_wrap, thì

pager_wrap echo {1..10000}

in một dòng rất dài đến thiết bị xuất chuẩn thay vì đường ống qua máy nhắn tin.


Có lẽ wc -lđang đếm các ký tự dòng mới, nhưng nếu nội dung là tất cả một dòng, nhưng kết thúc đủ để tắt thiết bị đầu cuối thì sao?

@TessellatingHeckler Đó là một điểm tốt, nhưng tôi không biết cách (hiệu quả) kiểm tra các hàng dài. Tôi cũng không thích thực tế là mã này đang lưu toàn bộ đầu ra trong một biến thay vì đệm ở hầu hết $LINEScác dòng, nhưng tôi không biết cách khắc phục điều đó, ...
AP

1
Vấn đề thực sự bạn đang cố gắng giải quyết là gì? Có lẽ bạn sẽ ổn với việc luôn luôn thực hiện các lệnh thông qua lessnếu nó tự động thoát nếu văn bản hiển thị tất cả trên một màn hình và không cần phân trang, và không xóa màn hình khi thoát? Bạn có thể làm điều đó - superuser.com/a/106644/67909

@TessellatingHeckler Điều đó sẽ ổn, miễn là nó không gây rối với bất kỳ đầu ra thiết bị đầu cuối nào trước đó. Tôi đã nghĩ về đường ống đến less -F, nhưng tôi đã bỏ nó đi vì nó không in lại đầu ra của đường ống. Bạn có ý kiển nào tốt hơn không? Dù sao, tôi hiện có hai trường hợp sử dụng: quấn quanh ls -lvà quấn quanh pacman -Ss [foo].
AP

Câu trả lời:


6

Tôi đã có một giải pháp được viết cho việc tuân thủ vỏ POSIX, nhưng tôi đã thử nghiệm nó chỉ trong bash, vì vậy tôi không biết chắc là nó có di động hay không. Và tôi không biết zsh, vì vậy tôi đã không cố gắng làm cho nó thân thiện với zsh. Bạn đặt lệnh của bạn vào nó; truyền một lệnh làm đối số cho một lệnh khác là một thiết kế xấu * .

Tất nhiên mọi giải pháp cho vấn đề này cần biết thiết bị đầu cuối có bao nhiêu hàng và cột. Trong mã dưới đây, tôi đã giả định rằng bạn có thể dựa vào các biến LINESCOLUMNSmôi trường ( lessnhìn vào). Các phương pháp đáng tin cậy hơn là:

  • sử dụng rows="${LINES:=$(tput lines)}"cols="${COLUMNS:=$(tput cols)}", theo đề xuất của AP , hoặc
  • nhìn vào đầu ra từ stty size. Lưu ý rằng lệnh này phải có thiết bị đầu cuối làm đầu vào tiêu chuẩn của nó, vì vậy, nếu đó là trong tập lệnh và bạn đang chuyển sang tập lệnh, bạn sẽ phải nói stty size <&1(trong bash) hoặc stty size < /dev/tty. Nắm bắt đầu ra của nó thậm chí còn phức tạp hơn.

Thành phần bí mật: foldlệnh sẽ ngắt các dòng dài theo cách màn hình sẽ có, do đó tập lệnh có thể xử lý các dòng dài một cách chính xác.

#!/bin/sh
buffer=$(mktemp)
rows="$LINES"
cols="$COLUMNS"
while true
do
      IFS= read -r some_data
      e=$?        # 1 if EOF, 0 if normal, successful read.
      printf "%s" "$some_data" >> "$buffer"
      if [ "$e" = 0 ]
      then
            printf "\n" >> "$buffer"
      fi
      if [ $(fold -w"$cols" "$buffer" | wc -l) -lt "$rows" ]
      then
            if [ "$e" != 0 ]
            then
                  cat "$buffer"
            else
                  continue
            fi
      else
            if [ "$e" != 0 ]
            then
                  "${PAGER:="less"}" < "$buffer"
                  # The above is equivalent to
                  # cat "$buffer"   | "${PAGER:="less"}"
                  # … but that’s a UUOC.
            else
                  cat "$buffer" - | "${PAGER:="less"}"
            fi
      fi
      break
done
rm "$buffer"

Để sử dụng điều này:

  • Đặt ở trên vào một tập tin; chúng ta hãy giả sử bạn gọi nó mypager.
  • (Tùy chọn) đặt nó vào một thư mục đó là đường dẫn tìm kiếm của bạn; ví dụ $HOME/bin.
  • Làm cho nó thực thi bằng cách gõ chmod +x mypager.
  • Sử dụng nó trong các lệnh như ps ax | mypagerhoặc ls -la | mypager.
    Nếu bạn bỏ qua bước thứ hai (đưa tập lệnh vào một thư mục là đường dẫn tìm kiếm của bạn), bạn sẽ phải làm , nơi có thể là một đường dẫn tương đối như siêu phạm .ps ax | path_to_mypager/mypagerpath_to_mypager.

* Tại sao việc truyền một lệnh làm (các) đối số cho một lệnh khác là một thiết kế xấu?

I. Tính thẩm mỹ / Sự phù hợp với truyền thống / Triết lý Unix

Unix có một triết lý về Do One Thing và Do It Well . Ví dụ: nếu một chương trình sẽ hiển thị dữ liệu theo một cách nhất định (như máy nhắn tin làm), thì nó cũng không nên gọi cơ chế tạo dữ liệu. Đó là những gì đường ống dành cho.

Không có nhiều chương trình Unix thực thi các lệnh hoặc chương trình do người dùng chỉ định. Hãy xem xét một số điều đó:

  • Shell, như trong Well, chạy các lệnh do người dùng chỉ định là công việc của shell ; Đó là điều duy nhất mà cái vỏ làm được. (Tất nhiên tôi không nói rằng shell là một chương trình đơn giản.)sh -c "command"
  • env, nice, nohup, setsid, su, Và sudo. Các chương trình này có điểm chung - tất cả đều tồn tại để chạy chương trình có môi trường thực thi được sửa đổi 1 . Họ phải làm việc theo cách họ làm, vì Unix thường không cho phép bạn thay đổi môi trường thực thi của một quy trình khác; bạn phải thay đổi quy trình của riêng mình, và sau đó forkvà / hoặc exec.
    _______
    1  Tôi đang sử dụng môi trường thực thi cụm từ theo nghĩa rộng, không chỉ đề cập đến các biến môi trường, mà còn xử lý các thuộc tính như nicegiá trị của Dinh, UID và GID, nhóm quy trình, ID phiên, thiết bị đầu cuối kiểm soát, tệp mở, thư mục làm việc , umaskgiá trị, ulimits, bố trí tín hiệu,alarm hẹn giờ, vv
  • Các chương trình cho phép thoát khỏi vỏ sò. Ví dụ duy nhất nảy sinh trong đầu là vi/ vim, mặc dù tôi khá chắc chắn rằng có những người khác. Đây là những cổ vật lịch sử. Họ có trước các hệ thống cửa sổ và thậm chí kiểm soát công việc; nếu bạn đang chỉnh sửa một tệp và bạn muốn làm một cái gì đó khác (như xem danh sách thư mục), bạn sẽ phải lưu tệp của mình và thoát khỏi trình chỉnh sửa để quay lại trình bao của bạn. Ngày nay, bạn có thể chuyển sang một cửa sổ khác hoặc sử dụng Ctrl+ Z(hoặc loại :suspend) để quay lại trình bao của mình trong khi giữ cho trình soạn thảo của bạn tồn tại, do đó, thoát khỏi trình bao, bị cho là lỗi thời.

Tôi không tính các chương trình thực thi các chương trình (mã hóa cứng) khác để tận dụng khả năng của chúng thay vì sao chép chúng. Ví dụ, một số chương trình có thể thực thi diffhoặc sort. (Ví dụ: có những câu chuyện kể rằng các phiên bản đầu spell được sử dụng sort -uđể lấy danh sách các từ được sử dụng trong tài liệu, và sau đó diff- hoặc có lẽ comm- để so sánh danh sách đó với danh sách từ trong từ điển và xác định từ nào trong tài liệu không có trong từ điển.)

II. Vấn đề thời gian

Cách tập lệnh của bạn được viết, RET="$($@)"dòng không hoàn thành cho đến khi lệnh được gọi hoàn thành. Do đó, tập lệnh của bạn không thể bắt đầu hiển thị dữ liệu cho đến khi lệnh tạo nó hoàn thành. Có lẽ cách đơn giản nhất để khắc phục đó là làm cho lệnh tạo dữ liệu tách biệt với chương trình hiển thị dữ liệu (mặc dù có nhiều cách khác).

III. Lịch sử chỉ huy

  1. Giả sử bạn chạy một số lệnh với đầu ra được xử lý bởi bộ lọc hiển thị của bạn và bạn nhìn vào đầu ra và quyết định rằng bạn muốn lưu đầu ra đó trong một tệp. Nếu bạn đã gõ (như một ví dụ giả định)

    ps ax | mypager

    sau đó bạn có thể gõ

    !:1 > myfile

    hoặc nhấn và chỉnh sửa dòng thích hợp. Bây giờ, nếu bạn đã gõ

    mypager "ps ax"

    bạn vẫn có thể quay lại và chỉnh sửa lệnh đó ps ax > myfile, nhưng nó không đơn giản như vậy.

  2. Hoặc giả sử bạn quyết định rằng bạn muốn chạy ps uaxtiếp theo. Nếu bạn đã gõ ps ax | mypager, bạn có thể làm

    !:0 u!:*

    Một lần nữa, với mypager "ps ax", nó vẫn có thể làm được, nhưng, có thể nói là khó hơn.

  3. Ngoài ra, hãy nhìn vào hai lệnh: ps ax | mypagermypager "ps ax". Giả sử bạn chạy một historydanh sách một giờ sau đó. ISTM mà bạn phải xem xét kỹ hơn mypager "ps ax"một chút để xem lệnh đang được thực thi là gì.

IV. Lệnh / trích dẫn phức tạp

  1. echo {1..10000}rõ ràng chỉ là một lệnh ví dụ; ps axkhông tốt hơn nhiều Điều gì nếu bạn muốn làm điều gì đó chỉ là một chút chút thực tế hơn, như ps ax | grep oracle? Nếu bạn gõ

    mypager ps ax | grep oracle

    nó sẽ chạy mypager ps ax và dẫn đầu ra từ đó thông qua grep oracle. Vì vậy, nếu đầu ra từ ps axdài 30 dòng, mypagersẽ gọi less, ngay cả khi đầu ra từ ps ax | grep oraclechỉ có 3 dòng. Có lẽ có những ví dụ sẽ thất bại trong một thời trang ấn tượng hơn.

    Vì vậy, bạn phải làm những gì tôi đã thể hiện trước đó:

    mypager "ps ax | grep oracle"

    Nhưng RET="$($@)"không thể xử lý điều đó. Tất nhiên, có nhiều cách để xử lý những việc như vậy, nhưng chúng không được khuyến khích.

  2. Điều gì xảy ra nếu dòng lệnh mà đầu ra bạn muốn nắm bắt thậm chí còn phức tạp hơn; ví dụ,

    lệnh 1   " arg 1 " |   lệnh 2   ' arg 2 ' $ ' arg 3 '

    nơi các đối số chứa kết hợp lộn xộn không gian, tab, $, |, \, <, >, *, ;, &, [, ], (, ), `, và thậm chí có '". Một lệnh như thế có thể đủ cứng để gõ trực tiếp vào shell một cách chính xác. Bây giờ hãy tưởng tượng cơn ác mộng của việc phải trích dẫn nó để vượt qua nó như là một đối số mypager.


Điều này thật tuyệt, cảm ơn! Thật kỳ lạ, tôi đã phải sử dụng rows="${LINES:=$(tput lines)}"cols="${COLUMNS:=$(tput cols)}", bởi vì tập lệnh không thể truy cập vào một trong hai biến môi trường, mặc dù, nói, echo $LINEShoạt động như mong đợi từ cli. Tôi cũng nhận xét ra printf "%s" "$e"dòng. Tôi chỉ còn lại một vài câu hỏi: 1. Bạn có thể giải thích tại sao việc truyền một lệnh làm đối số cho một lệnh khác lại là một thiết kế tồi không? Tôi hiểu rằng tốt hơn là tránh một ngã ba, nhưng có những lý do khác?
AP

(tiếp theo) 2. Tại sao tập lệnh của bạn gọi fold -w"$cols" "$buffer" | wc -lmỗi lần thay vì nói, giữ số lượng chạy fold -w"$cols" "$some_data" | wc -l?
AP

(0) Vâng, xin lỗi về printf "%s" "$e". Đó chỉ là một tuyên bố gỡ lỗi mà tôi không có ý định đăng trong câu trả lời của mình. (1) Tôi đã giải quyết # 1 trong câu trả lời. (2) Chà, fold -w"$cols" "$some_data" | wc -lsẽ không hiệu quả, nhưng tôi đoán tôi có thể làm được printf "%s" "$some_data" | fold -w"$cols" | wc -l, và thật lòng mà nói, tôi đã không nghĩ về nó. Tôi tin rằng cách tiếp cận của tôi đơn giản hơn một chút, nhưng ý tưởng của bạn sẽ hiệu quả hơn, đặc biệt là nếu $rowsnó lớn. (3) Xin lỗi vì mất quá nhiều thời gian để trả lời; một con gà tây băng qua đường, và tôi phải tìm hiểu tại sao. :-)
G-Man nói 'Phục hồi Monica'

Không vấn đề gì. Cảm ơn đã dành thời gian để trả lời các câu hỏi tiếp theo.
AP

1
@grundic Tôi cập nhật câu trả lời của tôi với một lời giải thích. Nếu bạn vẫn không hiểu, hãy mô tả chính xác những gì bạn đã nhầm lẫn.
G-Man nói 'Phục hồi Monica'

3

Đó là -Ftùy chọn lessdành cho, mặc dù bạn cũng cần sử dụng -Xtùy chọn này, nếu không, nó sẽ in văn bản sang màn hình thay thế trên các thiết bị đầu cuối có một (có nghĩa là nó sẽ không có sẵn khi lessthoát ra). Điều đó có thể thay đổi trong tương lai vì hiện tại có một yêu cầu nâng cao phải có -X ngụ ý khi văn bản vừa với một màn hình với -F(303)các hệ thống RedHat dường như đã có một bản vá cho điều đó kể từ năm 2008 (mặc dù nó chưa được đưa lên ngược dòng ( kể từ 2017-09-2014, tôi vừa gửi mail đến bug-less@gnu.org về điều đó)).

Vì thế:

cmd | less -RXF

Nếu bạn vẫn muốn sử dụng màn hình thay thế nếu đầu ra quá dài, đó là nơi bạn cần làm quen (trên các hệ thống không có bản vá RedHat đã đề cập ở trên):

page() {
  L=${LINES:-$(tput lines)} C=${COLUMNS:-$(tput cols)} \
    perl -Mopen=locale -MText::Tabs -MText::CharWidth=mbswidth -e '
      while(<STDIN>) {
        if ($pager) {
          print $pager $_;
        } else {
          chomp(my $line = $_);
          $line =~ s/\e\[[\d;]*m//g;
          $l += 1 + int(mbswidth(expand($line)) / $ENV{C});
          $buf .= $_;
          if ($l > $ENV{L}) {
            open $pager, "|-", "less", "-R", @ARGV or die "pager: $!";
            print $pager $buf;
          }
        }
      }
      print $buf unless $pager;' -- "$@"
}

Được sử dụng như:

cmd | page

hoặc là

page < file
page -S < file...

(không page file, nó chỉ có nghĩa là trang stdin).

Chúng tôi đang cố gắng đoán độ dài của đầu ra bằng cách tước các chuỗi thoát màu, mở rộng các tab và tính toán độ rộng để chúng tôi có thể xác định số lượng dòng thiết bị đầu cuối để hiển thị một dòng văn bản nhất định.

Điều đó sẽ hoạt động miễn là đầu ra không có các chuỗi thoát khác hoặc các ký tự điều khiển / mã hóa xấu.

Cũng lưu ý một điểm khác biệt đáng kể so với bản vá RedHat: đối với đầu ra một màn hình, đầu ra không trải qua quá trình lessxử lý hậu kỳ (như hiển thị các ký tự điều khiển như ^Xtrong video đảo ngược, nén các dòng trống bằng -s...). Mặc dù gần với những gì nó được hỏi ở đây, nhưng điều đó có thể ít được mong muốn hơn trong thực tế.

Bạn có thể phải cài đặt mô-đun Text :: CharWidth không phải là một trong những tiêu chuẩn ( libtext-charwidth-perlgói trên Debian).


1
hmm, trên máy của tôi ls | less -Fsẽ tự động sử dụng màn hình thay thế nếu đầu ra quá dài và không có nếu không. Tui bỏ lỡ điều gì vậy?
sourcejedi

@sourcejedi, không dành cho tôi, thậm chí với phiên bản 487 mới nhất. Bạn có một $LESSbiến với các tùy chọn bổ sung?
Stéphane Chazelas

@sourcejedi, xem chỉnh sửa và máy nhắn tin "ít": --quito-if-one-screen without --no-initless --quito-if-one-screen without --no-init dẫn đến pkgs.fedoraproject.org /cgit/rpms/less.git/tree/ . Bạn đang sử dụng một bản phân phối RedHat?
Stéphane Chazelas
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.