Số lượng ký tự trong đầu ra của lệnh shell


12

Tôi đang viết một kịch bản cần tính toán số lượng ký tự trong đầu ra của lệnh trong một bước duy nhất .

Ví dụ: sử dụng lệnh readlink -f /etc/fstabsẽ trả về 10vì đầu ra của lệnh đó dài 10 ký tự.

Điều này đã có thể với các biến được lưu trữ bằng cách sử dụng mã sau đây:

variable="somestring";
echo ${#variable};
# 10

Thật không may, sử dụng cùng một công thức với chuỗi được tạo bởi lệnh không hoạt động:

${#(readlink -f /etc/fstab)};
# bash: ${#(readlink -f /etc/fstab)}: bad substitution

Tôi hiểu rằng có thể làm điều này bằng cách trước tiên lưu đầu ra vào một biến:

variable=$(readlink -f /etc/fstab);
echo ${#variable};

Nhưng tôi muốn loại bỏ các bước thêm.

Điều này có thể không? Khả năng tương thích với vỏ Almquist (sh) chỉ sử dụng các tiện ích được xây dựng hoặc tiêu chuẩn là thích hợp hơn.


1
Đầu ra readlink -f /etc/fstab11 ký tự. Đừng quên dòng mới. Nếu không, bạn sẽ thấy /etc/fstabluser@cern:~$ khi bạn chạy nó từ một cái vỏ.
Phil Frost

@PhilFrost bạn dường như có một dấu nhắc vui, bạn có làm việc ở CERN không?
Dmitry Grigoryev

Câu trả lời:


9

Với expr GNU :

$ expr length + "$(readlink -f /etc/fstab)"
10

Các +có một tính năng đặc biệt của GNU exprđể đảm bảo các số tiếp theo được coi là một chuỗi ngay cả khi nó xảy ra là một exprnhà điều hành như match, length, +...

Ở trên sẽ loại bỏ bất kỳ dòng mới của đầu ra. Để làm việc xung quanh nó:

$ expr length + "$(readlink -f /etc/fstab; printf .)" - 2
10

Kết quả bị trừ đi 2 vì dòng mới cuối cùng readlinkvà ký tự .chúng tôi đã thêm.

Với chuỗi Unicode, exprdường như không hoạt động, bởi vì nó trả về độ dài của chuỗi theo byte thay vì đếm ký tự (Xem dòng 654 )

$ LC_ALL=C.UTF-8 expr length ăaa
4

Vì vậy, bạn có thể sử dụng:

$ printf "ăaa" | LC_ALL=C.UTF-8 wc -m
3

VỊ TRÍ:

$ expr " $(readlink -f /etc/fstab; printf .)" : ".*" - 3
10

Không gian trước khi thay thế lệnh ngăn lệnh bị lỗi với chuỗi bắt đầu bằng -, vì vậy chúng ta cần trừ 3.


Cảm ơn! Có vẻ như ví dụ thứ ba của bạn hoạt động ngay cả khi không có LC_ALL=C.UTF-8, điều này đơn giản hóa đáng kể mọi thứ nếu mã hóa của chuỗi sẽ không được biết trước.
dùng339676

2
expr length $(echo "*")- Không. Ít nhất sử dụng dấu ngoặc kép : expr length "$(…)". Nhưng điều này loại bỏ các dòng mới từ lệnh, đó là một tính năng không thể thay thế của lệnh thay thế. (Bạn có thể làm việc xung quanh nó, nhưng sau đó câu trả lời thậm chí còn phức tạp hơn.)
Gilles 'SO- ngừng trở nên xấu xa'

6

Không chắc chắn làm thế nào để làm điều này với các nội dung shell ( mặc dù Gnouc ) nhưng các công cụ tiêu chuẩn có thể giúp:

  1. Bạn có thể sử dụng wc -mmà đếm các ký tự. Thật không may, nó cũng tính dòng mới cuối cùng nên bạn phải loại bỏ nó trước:

    readlink -f /etc/fstab | tr -d '\n' | wc -m
  2. Tất nhiên bạn có thể sử dụng awk

    readlink -f /etc/fstab | awk '{print length($0)}'
  3. Hoặc Perl

    readlink -f /etc/fstab | perl -lne 'print length'

Bạn có nghĩa exprlà một tích hợp? Trong vỏ nào?
mikeerv

5

Tôi thường làm như thế này:

$ echo -n "$variable" | wc -m
10

Để thực hiện các lệnh tôi sẽ điều chỉnh nó như vậy:

$ echo -n "$(readlink -f /etc/fstab)" | wc -m
10

Cách tiếp cận này tương tự như những gì bạn đã làm trong 2 bước của mình, ngoại trừ việc chúng tôi kết hợp chúng thành một lớp lót duy nhất.


2
Bạn phải sử dụng -mthay vì -c. Với các ký tự unicode, cách tiếp cận của bạn sẽ bị phá vỡ.
cuonglm

1
Tại sao không đơn giản readlink -f /etc/fstab | wc -m?
Phil Frost

1
Tại sao bạn sử dụng phương pháp không đáng tin cậy này thay vì ${#variable}? Ít nhất sử dụng dấu ngoặc kép echo -n "$variable", nhưng điều này vẫn thất bại nếu như giá trị của variable-e. Khi bạn sử dụng nó kết hợp với thay thế lệnh, hãy nhớ rằng các dòng mới bị loại bỏ.
Gilles 'SO- ngừng trở nên xấu xa'

@philfrost b / c những gì tôi đã thể hiện dựa trên những gì op đã nghĩ. Ngoài ra, nó hoạt động cho bất kỳ cmds nào mà anh ta có thể đã thiết lập trước trong vars và muốn có các mật khẩu dài. Ngoài ra terdon có ví dụ đó rồi.
slm

1

Bạn có thể gọi các tiện ích bên ngoài (xem các câu trả lời khác), nhưng chúng sẽ làm cho tập lệnh của bạn chậm hơn và thật khó để có được hệ thống ống nước đúng.

Zsh

Trong zsh, bạn có thể viết ${#$(readlink -f /etc/fstab)}để lấy độ dài của lệnh thay thế. Lưu ý rằng đây không phải là độ dài của đầu ra lệnh, nó là độ dài của đầu ra mà không có bất kỳ dòng mới nào.

Nếu bạn muốn độ dài chính xác của đầu ra, hãy xuất thêm một ký tự không phải dòng mới ở cuối và trừ đi một ký tự.

$((${#$(readlink -f /etc/fstab; echo .)} - 1))

Nếu những gì bạn muốn là tải trọng trong đầu ra của lệnh, thì bạn cần trừ hai ở đây, bởi vì đầu ra của readlink -flà đường dẫn chính tắc cộng với một dòng mới.

$((${#$(readlink -f /etc/fstab; echo .)} - 2))

Điều này khác với ${#$(readlink -f /etc/fstab)}trong trường hợp hiếm hoi nhưng có thể xảy ra khi chính đường dẫn chính tắc kết thúc trong một dòng mới.

Đối với ví dụ cụ thể này, bạn hoàn toàn không cần một tiện ích bên ngoài, bởi vì zsh có cấu trúc tích hợp tương đương với readlink -f, thông qua công cụ sửa đổi lịch sử A.

echo /etc/fstab(:A)

Để có được độ dài, hãy sử dụng công cụ sửa đổi lịch sử trong việc mở rộng tham số:

${#${:-/etc/fstab}:A}

Nếu bạn có tên tệp trong một biến filename, đó sẽ là ${#filename:A}.

Vỏ kiểu Bourne / POSIX

Không có vỏ Bourne / POSIX thuần túy nào (Bourne, tro, mksh, ksh93, bash, yash,) có bất kỳ phần mở rộng tương tự nào mà tôi biết. Nếu bạn cần áp dụng thay thế tham số cho đầu ra của thay thế lệnh hoặc thay thế tham số lồng nhau, hãy sử dụng các giai đoạn kế tiếp.

Bạn có thể nhồi công cụ xử lý vào một chức năng nếu bạn muốn.

command_output_length_sans_trailing_newlines () {
  set -- "$("$@")"
  echo "${#1}"
}

hoặc là

command_output_length () {
  set -- "$("$@"; echo .)"
  echo "$((${#1} - 1))"
}

nhưng thường không có lợi ích gì; ngoại trừ với ksh93, điều đó làm cho một ngã ba phụ có thể sử dụng đầu ra của hàm, do đó, nó làm cho tập lệnh của bạn chậm hơn và hiếm khi có bất kỳ lợi ích dễ đọc nào.

Một lần nữa, đầu ra của readlink -flà đường dẫn chính tắc cộng với một dòng mới; nếu bạn muốn độ dài của đường dẫn chính tắc, hãy trừ 2 thay vì 1 in command_output_length. Việc sử dụng chỉ command_output_length_sans_trailing_newlinesmang lại kết quả đúng khi đường dẫn chính không kết thúc ở dòng mới.

Byte vs ký tự

${#…}được cho là độ dài tính bằng ký tự, không tính bằng byte, điều này tạo ra sự khác biệt về vị trí đa bào. Các phiên bản cập nhật hợp lý của ksh93, bash và zsh tính toán độ dài tính bằng ký tự theo giá trị LC_CTYPEtại thời điểm ${#…}cấu trúc được mở rộng. Nhiều hệ vỏ phổ biến khác không thực sự hỗ trợ các địa điểm đa bào: như dấu gạch ngang 0,5,7, mksh 46 và posh 0.12.3, ${#…}trả về độ dài tính bằng byte. Nếu bạn muốn độ dài của các ký tự một cách đáng tin cậy, hãy sử dụng wctiện ích:

$(readlink -f /etc/fstab | wc -m)

Miễn là $LC_CTYPEchỉ định một miền địa phương hợp lệ, bạn có thể tin tưởng rằng điều này sẽ xảy ra lỗi (trên nền tảng cổ xưa hoặc bị hạn chế không hỗ trợ các địa điểm đa nhân) hoặc trả về độ dài chính xác trong các ký tự. (Đối với Unicode, độ dài của dòng chữ trong ký tự, có nghĩa là số lượng điểm mã - số lượng glyphs là một câu chuyện khác, do các biến chứng như kết hợp các ký tự.)

Nếu bạn muốn độ dài tính bằng byte, hãy đặt LC_CTYPE=Ctạm thời hoặc sử dụng wc -cthay vì wc -m.

Đếm các byte hoặc ký tự wcbao gồm bất kỳ dòng mới nào từ lệnh. Nếu bạn muốn độ dài của đường dẫn chính tắc tính bằng byte, thì đó là

$(($(readlink -f /etc/fstab | wc -c) - 1))

Để có được nó trong các ký tự, trừ 2.


@cuonglm Không, bạn cần trừ 1. echo .thêm hai ký tự, nhưng ký tự thứ hai là một dòng mới ở cuối bị tước bởi thay thế lệnh.
Gilles 'SO- ngừng trở nên xấu xa'

Dòng mới là từ readlinkđầu ra, cộng với .bởi echo. Cả hai chúng tôi đều đồng ý rằng echo .thêm hai ký tự nhưng dòng mới bị xóa. Hãy thử với printf .hoặc xem câu trả lời của tôi unix.stackexchange.com/a/160499/38906 .
cuonglm

@cuonglm Câu hỏi hỏi số lượng ký tự trong đầu ra của lệnh. Đầu ra của readlinklà mục tiêu liên kết cộng với một dòng mới.
Gilles 'SO- ngừng trở nên xấu xa'

0

Điều này hoạt động trong dashnhưng nó yêu cầu var được nhắm mục tiêu chắc chắn là trống hoặc không được đặt. Đó là lý do tại sao đây thực sự là hai lệnh - tôi rõ ràng trống $ltrong phần đầu tiên:

l=;printf '%.slen is %d and result is %s\n' \
    "${l:=$(readlink -f /etc/fstab)}" "${#l}" "$l"

ĐẦU RA

len is 10 and result is /etc/fstab

Đó là tất cả các nội dung shell - tất nhiên không bao gồm readlink- nhưng đánh giá nó trong shell hiện tại theo cách đó ngụ ý rằng bạn phải thực hiện chuyển nhượng trước khi nhận được len, đó là lý do tại sao tôi %.sbỏ qua đối số đầu tiên trong printfchuỗi định dạng và chỉ cần thêm lại nó cho giá trị theo nghĩa đen ở phần đuôi của printfdanh sách arg.

Với eval:

l=$(readlink -f /etc/fstab) eval 'l=${#l}:$l'
printf %s\\n "$l"

ĐẦU RA

10:/etc/fstab

Bạn có thể tiến gần đến điều tương tự, nhưng thay vì đầu ra trong một biến trong lệnh đầu tiên, bạn nhận được nó trên thiết bị xuất chuẩn:

PS4='${#0}:$0' dash -cx '2>&1' "$(readlink -f /etc/fstab)"

... viết ...

10:/etc/fstab

... Để mô tả tệp 1 mà không gán bất kỳ giá trị nào cho bất kỳ vars nào trong trình bao hiện tại.


1
Đó không phải là chính xác những gì OP muốn tránh sao? "Tôi hiểu rằng có thể làm điều này bằng cách trước tiên lưu đầu ra vào một biến: variable=$(readlink -f /etc/fstab); echo ${#variable};Nhưng tôi muốn loại bỏ bước bổ sung."
terdon

@terdon, có lẽ tôi đã hiểu nhầm, nhưng đó là ấn tượng của tôi rằng dấu chấm phẩy là vấn đề chứ không phải là biến. Đó là lý do tại sao chúng nhận được len và đầu ra trong một lệnh đơn giản chỉ sử dụng các nội trang shell. Vỏ không readlink exec sau đó exec expr, ví dụ. Có lẽ chỉ có vấn đề nếu bằng cách nào đó nhận được len có giá trị, mà tôi thừa nhận tôi đang gặp khó khăn để hiểu tại sao điều đó có thể xảy ra, nhưng tôi nghi ngờ có thể có một trường hợp quan trọng.
mikeerv

1
Nhân tiện eval, có lẽ là sạch nhất ở đây - nó gán đầu ra và len cho cùng một tên var trong một lần thực thi - rất gần với việc thực hiện l=length(l):out(l). Nhân tiện, việc expr length $(command) này không bao gồm các giá trị có lợi cho len.
mikeerv
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.