Chia đầu ra của lệnh theo các cột bằng cách sử dụng Bash?


87

Tôi muốn làm điều này:

  1. chạy một lệnh
  2. nắm bắt đầu ra
  3. chọn một dòng
  4. chọn một cột của dòng đó

Chỉ là một ví dụ, giả sử tôi muốn lấy tên lệnh từ a $PID(xin lưu ý rằng đây chỉ là một ví dụ, tôi không đề xuất đây là cách dễ nhất để lấy tên lệnh từ id quy trình - vấn đề thực sự của tôi là với một lệnh khác có định dạng đầu ra mà tôi không thể kiểm soát).

Nếu tôi chạy pstôi nhận được:


  PID TTY          TIME CMD
11383 pts/1    00:00:00 bash
11771 pts/1    00:00:00 ps

Bây giờ tôi làm ps | egrep 11383và nhận được

11383 pts/1    00:00:00 bash

Bước tiếp theo: ps | egrep 11383 | cut -d" " -f 4. Đầu ra là:

<absolutely nothing/>

Vấn đề là cutcắt đầu ra bởi các khoảng trắng đơn và khi psthêm một số khoảng trắng giữa cột thứ 2 và thứ 3 để giữ một số điểm tương đồng với một bảng, cutchọn một chuỗi trống. Tất nhiên, tôi có thể sử dụng cutđể chọn trường thứ 7 chứ không phải trường thứ 4, nhưng làm thế nào tôi có thể biết được, đặc biệt là khi đầu ra có thể thay đổi và chưa biết trước.


2
Sử dụng awk (và 25 ký tự khác).
Michael Foukarakis

Câu trả lời:


178

Một cách dễ dàng là thêm một đường chuyền trđể loại bỏ bất kỳ dấu phân cách trường lặp lại nào:

$ ps | egrep 11383 | tr -s ' ' | cut -d ' ' -f 4

1
Tôi như thế này, trông giống như trlà trọng lượng nhẹ hơnawk
các hệ thống kiểm soát bay

3
Tôi có xu hướng đồng ý, nhưng đó cũng có thể là do tôi chưa học được kỹ năng nào. :)
thư giãn

Sẽ không hoạt động nếu bạn tình cờ có một quy trình với PID có chứa PID mà bạn quan tâm như một phép trừ.
David Grayson

1
Ngoài ra, các niber trường sẽ tắt nếu một số PID: s được đệm khoảng trắng ở bên trái trong khi những PID khác thì không.
tripleee

68

Tôi nghĩ cách đơn giản nhất là sử dụng awk . Thí dụ:

$ echo "11383 pts/1    00:00:00 bash" | awk '{ print $4; }'
bash

4
Để tương thích với câu hỏi ban đầu, ps | awk "\$1==$PID{print\$4}"hoặc (tốt hơn) ps | awk -v"PID=$PID" '$1=PID{print$4}'. Tất nhiên, trên Linux, bạn chỉ có thể làm xargs -0n1 </proc/$PID/cmdline | head -n1hoặc readlink /proc/$PID/exe, nhưng dù sao đi nữa ...
ephemient 27/10/09

;trong { print $4; }yêu cầu? Việc xóa nó dường như không có tác dụng gì đối với tôi trên Linux, tôi chỉ tò mò về mục đích của nó
fireteflow

@igniteflow nó sẽ không chỉ ra sự kết thúc của lệnh nếu bạn muốn tiếp tục thêm vào câu lệnh in đã qua?
joshmcode 26/02/19

16

Xin lưu ý rằng tr -s ' 'tùy chọn này sẽ không loại bỏ bất kỳ khoảng trắng đầu nào. Nếu cột của bạn được căn phải (như với pspid) ...

$ ps h -o pid,user -C ssh,sshd | tr -s " "
 1543 root
19645 root
19731 root

Sau đó, việc cắt sẽ dẫn đến một dòng trống cho một số trường đó nếu đó là cột đầu tiên:

$ <previous command> | cut -d ' ' -f1

19645
19731

Trừ khi bạn đặt trước nó bằng một khoảng trắng, rõ ràng

$ <command> | sed -e "s/.*/ &/" | tr -s " "

Bây giờ, đối với trường hợp cụ thể của số pid (không phải tên), có một hàm được gọi là pgrep:

$ pgrep ssh


Các chức năng của vỏ

Tuy nhiên, nói chung vẫn có thể sử dụng các hàm shell một cách ngắn gọn, bởi vì có một điều gọn gàng về readlệnh:

$ <command> | while read a b; do echo $a; done

Tham số đầu tiên để đọc, achọn cột đầu tiên và nếu có nhiều hơn, mọi thứ khác sẽ được đưa vào b. Do đó, bạn không bao giờ cần nhiều biến hơn số cột +1 của mình .

Vì thế,

while read a b c d; do echo $c; done

sau đó sẽ xuất ra cột thứ 3. Như đã nêu trong nhận xét của tôi ...

Một lần đọc theo đường ống sẽ được thực thi trong một môi trường không chuyển các biến cho tập lệnh gọi.

out=$(ps whatever | { read a b c d; echo $c; })

arr=($(ps whatever | { read a b c d; echo $c $b; }))
echo ${arr[1]}     # will output 'b'`


Giải pháp mảng

Vì vậy, sau đó chúng tôi kết thúc với câu trả lời của @frayser là sử dụng biến shell IFS mặc định là một khoảng trắng, để chia chuỗi thành một mảng. Nó chỉ hoạt động trong Bash. Dash và Ash không hỗ trợ nó. Tôi đã gặp khó khăn khi tách một chuỗi thành các thành phần trong một thứ Busybox. Thật dễ dàng để lấy một thành phần duy nhất (ví dụ: sử dụng awk) và sau đó lặp lại điều đó cho mọi tham số bạn cần. Nhưng sau đó bạn liên tục gọi awk trên cùng một dòng hoặc liên tục sử dụng khối đọc có echo trên cùng một dòng. Cái nào không hiệu quả hoặc không đẹp. Vì vậy, bạn kết thúc việc chia nhỏ bằng cách sử dụng ${name%% *}và như thế. Khiến bạn khao khát một số kỹ năng Python vì trên thực tế, viết kịch bản shell không còn thú vị nữa nếu một nửa hoặc nhiều tính năng bạn quen thuộc đã biến mất. Nhưng bạn có thể giả định rằng ngay cả python cũng sẽ không được cài đặt trên hệ thống như vậy và nó không phải vậy ;-).


Bạn nên sử dụng dấu ngoặc kép xung quanh biến trong echo "$a"echo "$c"mặc dù.
tripleee

Có vẻ như mọi khối đường ống được thực thi trong vỏ con hoặc quy trình của riêng nó và bạn không thể trả về bất kỳ biến nào cho khối bao quanh? Mặc dù bạn có thể nhận được đầu ra của nó sau khi lặp lại nó. var=$(....... | { read a b c d; echo $c; }). Điều đó chỉ hoạt động cho một (chuỗi) duy nhất, mặc dù trong Bash, bạn có thể chia nó thành một mảng bằng cách sử dụngar=($var)
Xennex81

@tripleee Tôi không nghĩ đó là vấn đề ở giai đoạn như vậy của quá trình. Bạn sẽ sớm khám phá ra liệu bạn có cần điều đó hay không, và nếu điều đó xảy ra vào một lúc nào đó, đó là một bài học kinh nghiệm. Và sau đó bạn biết tại sao bạn phải sử dụng những dấu ngoặc kép đó ;-). Và sau đó nó không còn là điều bạn đã nghe người khác nói nữa. Chơi với lửa! : D. : p.
Xennex81

câu trả lời chi tiết: D
ncomputers

Đây là một câu trả lời quá hữu ích để tôi không thể nói như vậy.
Ivan X

4

thử

ps |&
while read -p first second third fourth etc ; do
   if [[ $first == '11383' ]]
   then
       echo got: $fourth
   fi       
done

1
@flybywire - có thể quá mức cần thiết đối với ví dụ đơn giản này, nhưng thành ngữ này rất hay nếu bạn cần thực hiện xử lý phức tạp hơn trên dữ liệu đã chọn.
James Anderson

Ngoài ra, hãy lưu ý rằng những ngày này, shell script mặc định thường không bash.
David Given

2

Sử dụng biến mảng

set $(ps | egrep "^11383 "); echo $4

hoặc là

A=( $(ps | egrep "^11383 ") ) ; echo ${A[3]}

2

Tương tự như giải pháp awk của brianegge, đây là giải pháp tương đương Perl:

ps | egrep 11383 | perl -lane 'print $F[3]'

-abật chế độ đèn tự động, chế độ này điền vào @Fmảng với dữ liệu cột.
Sử dụng-F, nếu dữ liệu của bạn được phân cách bằng dấu phẩy, thay vì phân cách bằng dấu cách.

Trường 3 được in vì Perl bắt đầu đếm từ 0 thay vì 1


1
Cảm ơn bạn về giải pháp perl của bạn - không biết về autosplit, và vẫn nghĩ perl là công cụ để kết thúc các công cụ khác ..;).
Gerard ONeill

1

Lấy đúng dòng (ví dụ cho dòng số 6) được thực hiện với đầu và đuôi và từ chính xác (từ số 4) có thể được bắt bằng awk:

command|head -n 6|tail -n 1|awk '{print $4}'

Chỉ cần lưu ý cho độc giả trong tương lai mà awk có thể chọn bằng dòng cũng như: awk NR=6 {print $4}sẽ có một chút hiệu quả hơn
David Z

1
và do đó tất nhiên tôi có nghĩa là awk NR==6 {print $4}* doh *
David Z

1

Lệnh của bạn

ps | egrep 11383 | cut -d" " -f 4

bỏ lỡ một tr -skhoảng trống, như giải thích thư giãn trong câu trả lời của mình .

Tuy nhiên, bạn có thể muốn sử dụng awk, vì nó xử lý tất cả các hành động này trong một lệnh duy nhất:

ps | awk '/11383/ {print $4}'

Điều này in ra cột thứ 4 trong những dòng có chứa 11383. Nếu bạn muốn điều này khớp 11383nếu nó xuất hiện ở đầu dòng, thì bạn có thể nói ps | awk '/^11383/ {print $4}'.


0

Thay vì làm tất cả những thứ này, tôi khuyên bạn nên sử dụng khả năng thay đổi định dạng đầu ra của ps.

ps -o cmd= -p 12345

Bạn nhận được dòng cmmand của một quá trình với pid được chỉ định và không có gì khác.

Điều này tuân theo POSIX và do đó có thể được coi là di động.


1
flybywire nói rằng anh ấy chỉ đang sử dụng ps làm ví dụ, câu hỏi còn chung chung hơn thế.
Ogre Psalm33:

0

Bash setsẽ phân tích cú pháp tất cả đầu ra thành các tham số vị trí.

Ví dụ: với set $(free -h)lệnh, echo $7sẽ hiển thị "Mem:"


Phương thức này chỉ hữu ích khi lệnh có một dòng đầu ra. Không đủ chung chung.
codeforester

Điều đó không đúng, tất cả đầu ra được đặt vào các tham số vị trí bất kể dòng nào. người yêu cũ set $(sar -r 1 1); echo "${23}"
dman

Quan điểm của tôi là rất khó để xác định vị trí của đối số khi đầu ra quá lớn và có nhiều trường. awklà cách tốt nhất để đi về nó.
codeforester

Đây chỉ là một giải pháp khác. OP có thể không muốn học ngôn ngữ awk cho trường hợp sử dụng duy nhất này. Các thẻ có trạng thái bashvà không awk.
dman
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.