Lệnh chỉ in 3 ký tự cuối của chuỗi


30

Tôi biết rằng cutlệnh có thể in các nký tự đầu tiên của chuỗi nhưng làm thế nào để chọn các nký tự cuối cùng ?

Nếu tôi có một chuỗi có số lượng ký tự thay đổi, làm thế nào tôi chỉ có thể in ba ký tự cuối cùng của chuỗi. ví dụ.

sản lượng "không giới hạn" cần thiết là "ted"
"987654" đầu ra cần thiết là "654"
Đầu ra "123456789" cần là "789"

Câu trả lời:


52

Tại sao không ai đưa ra câu trả lời rõ ràng?

sed 's/.*\(...\)/\1/'

Sồi hoặc hơi ít rõ ràng

grep -o '...$'

Phải thừa nhận rằng, cái thứ hai có nhược điểm là các dòng có ít hơn ba ký tự biến mất; nhưng câu hỏi không xác định rõ ràng hành vi cho trường hợp này.


6
hoặcgrep -o '.\{3\}$'
Avinash Raj

3
hoặcecho "unlimited" | python -c "print raw_input()[-3:]"
Kiro

8
@Kiro hoặc "echo unlimited" | java -jar EnterpriseWordTrimmer.jar, nhưng tôi không nghĩ thực sự cần thiết phải mang một ngôn ngữ nặng hơn để thao túng nhân vật.
wchargein

11
@WChargin bạn đã quênjava -server -Xms300M -Xmx3G -XX:+UseParallelGC -cp /path/to/all/the/jars/ -Dinput.interactive=false -Dinput.pipe=true -Dconfig.file=/path/to/config/last-three-letters.cfg -jar ...
hjk

6
grep -o -P '.{0,3}$'sẽ in 3 ký tự cuối ngay cả khi dòng có ít hơn 3 ký tự. -Ptránh phải thoát khỏi niềng răng.
Raghu Dodda

43

Giữ cho nó đơn giản - đuôi

Chúng ta không cần một biểu thức chính quy, hoặc nhiều hơn một quá trình, chỉ để đếm các ký tự.
Lệnh tail, thường được sử dụng để hiển thị các dòng cuối cùng của tệp, có một tùy chọn -c( --bytes), dường như chỉ là công cụ phù hợp cho việc này:

$ printf 123456789 | tail -c 3
789

(Khi bạn ở trong một vỏ, sẽ hợp lý khi sử dụng một phương thức như trong câu trả lời của mikeerv, vì nó tiết kiệm bắt đầu quá trình cho tail.)

Ký tự Unicode thực sự?

Bây giờ, bạn yêu cầu ba ký tự cuối cùng ; Đó không phải là những gì câu trả lời này mang lại cho bạn: nó tạo ra ba byte cuối cùng !

Miễn là mỗi ký tự là một byte, tail -cchỉ cần hoạt động. Vì vậy, nó có thể được sử dụng nếu bộ ký tự là ASCII, ISO 8859-1hoặc một biến thể.

Nếu bạn có đầu vào Unicode, như trong UTF-8định dạng phổ biến , kết quả là sai:

$ printf 123αβγ | tail -c 3
�γ

Trong ví dụ này, sử dụng UTF-8, các ký tự Hy Lạp alpha, beta và gamma dài hai byte:

$ printf 123αβγ | wc -c  
9

Tùy chọn -mít nhất có thể đếm các ký tự unicode thực:

printf 123αβγ | wc -m
6

Ok, vì vậy 6 byte cuối cùng sẽ cung cấp cho chúng ta 3 ký tự cuối cùng:

$ printf 123αβγ | tail -c 6
αβγ

Vì vậy, tailkhông hỗ trợ xử lý các ký tự chung và thậm chí không thử (xem bên dưới): Nó xử lý các dòng kích thước thay đổi, nhưng không có ký tự kích thước thay đổi.

Chúng ta hãy giải thích theo cách này: tailđúng với cấu trúc của vấn đề cần giải quyết, nhưng sai đối với loại dữ liệu.

GNU coreutils

Nhìn xa hơn, nó chỉ ra rằng ngươi coreutils GNU, bộ sưu tập các công cụ cơ bản như sed, ls, tailcut, không được quốc tế hóa chưa đầy đủ. Mà chủ yếu là về hỗ trợ Unicode.
Ví dụ, cutsẽ là một ứng cử viên tốt để sử dụng thay vì đuôi ở đây để hỗ trợ nhân vật; Nó có các tùy chọn để làm việc trên byte hoặc ký tự, -c( --bytes) và -m( --chars);

Duy nhất mà -m/ --charslà, như các phiên bản
cut (GNU coreutils) 8.21năm 2013,
không được thực hiện!

Từ info cut:

`-c CHARACTER-LIST'
`--characters=CHARACTER-LIST'
     Select for printing only the characters in positions listed in CHARACTER-LIST.  
     The same as `-b' for now, but internationalization will change that.


Xem thêm câu trả lời này để Không thể sử dụng `cut -c` (` --char character`) với UTF-8? .


2
Trên thực tế, hầu hết các câu trả lời khác dường như xử lý Unicode tốt, miễn là ngôn ngữ hiện tại chỉ định mã hóa UTF-8. Chỉ có cutgiải pháp dựa trên cơ sở của bạn và glenn jackman dường như không.
Ilmari Karonen

@IlmariKaronen Đúng, cảm ơn vì gợi ý. Tôi đã chỉnh sửa, với một số chi tiết bổ sung.
Volker Siegel

1
Lưu ý rằng POSIX chỉ định rõ ràng rằng tailnên xử lý byte và không phải ký tự. Tôi đã từng tạo một bản vá để thêm một tùy chọn mới để chọn các ký tự, nhưng tôi tin rằng không bao giờ được hợp nhất: - /
Martin Tournoij

Không hoạt động ở chế độ tệp, nhưtail -c3 -n10 /var/log/syslog
Suncatcher

@Suncatcher Tôi đã thử, và nó đã hoạt động. Vấn đề bạn nhìn thấy là gì? Lệnh của bạn tail -c3 -n10 /var/log/syslogyêu cầu 10 dòng cuối cùng, và điều đó làm việc cho tôi. Bạn sử dụng tùy chọn -c3và sau đó tùy chọn xung đột -n10. Các tùy chọn sau được ưu tiên.
Volker Siegel

36

Nếu văn bản của bạn là trong một biến vỏ gọi STRING, bạn có thể làm điều này trong một bash, zshhoặc mkshshell:

printf '%s\n' "${STRING:(-3)}"

Hoặc là

printf '%s\n' "${STRING: -3}"

cũng có lợi ích khi làm việc với ksh93, cú pháp đó xuất phát từ đâu.

Vấn đề là :phải tách ra khỏi -, nếu không nó sẽ trở thành ${var:-default}toán tử của vỏ Bourne.

Cú pháp tương đương trong zshhoặc yashshell là:

printf '%s\n' "${STRING[-3,-1]}"

2
Loại cú pháp / thao tác được gọi là gì để tôi có thể tìm kiếm thêm thông tin?
Tulains Córdova

6
Nó được gọi là mở rộng chuỗi con . Đó là một loại mở rộng tham số . Biểu mẫu chung là $ {tham số: offset: length} , nhưng trường độ dài là tùy chọn (và, như bạn có thể thấy, nó đã bị bỏ qua trong câu trả lời ở trên). DopeGhoti cũng có thể đã viết ${STRING:(-3):3}(chỉ định trường độ dài ), ${STRING: -3}(với khoảng trắng giữa :-), hoặc ${STRING: -3:3}.
G-Man nói 'Phục hồi Monica'

Trong trường hợp này, việc chỉ định độ dài 3có phần hơi giống như yêu cầu "ba nhân vật từ nhân vật thứ ba từ nhân vật cuối cùng, bao gồm" điều này xảy ra là một hoạt động giống hệt nhau về mặt thực tế với "Tất cả các nhân vật trở đi từ người thứ ba từ người cuối cùng , bao gồm ".
DopeGhoti

13

Sử dụng awk:

awk '{ print substr( $0, length($0) - 2, length($0) ) }' file
ted
654
789

11

Nếu chuỗi nằm trong một biến bạn có thể làm:

printf %s\\n "${var#"${var%???}"}"

Điều đó tước ba ký tự cuối cùng từ giá trị $varnhư:

${var%???}

... và sau đó thoát khỏi đầu của $vartất cả mọi thứ, nhưng những gì vừa bị tước đi như sau:

${var#"${var%???}"}

Phương pháp này có những mặt thăng trầm. Về mặt sáng sủa, nó hoàn toàn có thể mang theo POSIX và hoạt động trong mọi vỏ hiện đại. Ngoài ra, nếu $varkhông chứa ít nhất ba nhân vật không có gì nhưng trailing \newline được in ra. Sau đó, một lần nữa, nếu bạn muốn nó được in trong trường hợp đó, bạn cần một bước bổ sung như:

last3=${var#"${var%???}"}
printf %s\\n "${last3:-$var}"

Theo cách đó, $last3chỉ bao giờ trống nếu $varchứa 3 hoặc ít hơn byte. Và $varchỉ được thay thế cho $last3nếu $last3trống hoặc unset- và chúng tôi biết đó không phải là unsetvì chúng tôi chỉ đặt nó.


Điều đó khá gọn gàng +1. Ngoài ra: bất kỳ lý do bạn không trích dẫn printfchuỗi định dạng của bạn ?
jasonwryan

Tại sao không chỉ sử dụng ${VARNAME:(-3)}(giả định bash)?
DopeGhoti

1
Cảm ơn đã làm rõ; có ý nghĩa, ngay cả khi nó trông (đối với tôi) hơi kỳ lạ ...
jasonwryan

1
@DopeGhoti - đơn giản vì đó là một giả định tôi gần như không bao giờ thực hiện. Điều này hoạt động cũng bashnhư trong bất kỳ vỏ nào khác yêu cầu tính khả dụng của POSIX.
mikeerv

3
@odyssey - Vấn đề là cshkhông trong hiện đại, POSIX tương thích với vỏ tôi đề cập ở đây, không may. Thông số kỹ thuật vỏ POSIX được mô hình hóa sau ksh, mô hình chính nó sau khi kết hợp cả hai cshvà vỏ kiểu Bourne truyền thống. kshkết hợp cả cshchức năng kiểm soát công việc tuyệt vời và chuyển hướng i / o kiểu cũ của Bourne. Nó cũng thêm một số thứ - chẳng hạn như các khái niệm thao tác chuỗi tôi trình bày ở trên. Điều này sẽ không có khả năng làm việc trong bất kỳ truyền thống cshnhư tôi biết, tôi rất tiếc phải nói.
mikeerv

7

Bạn có thể làm điều này, nhưng điều này hơi ... quá mức:

for s in unlimited 987654 123456789; do
    rev <<< $s | cut -c 1-3 | rev
done 
ted
654
789

3

Giải pháp chống đạn cho chuỗi utf-8:

utf8_str=$'\xd0\xbf\xd1\x80\xd0\xb8\xd0\xb2\xd0\xb5\xd1\x82' # привет

last_three_chars=$(perl -CAO -e 'print substr($ARGV[0], -3)' "$utf8_str")

Hoặc dùng:

last_three_chars=$(perl -MEncode -CO -e '
  print substr(decode("UTF-8", $ARGV[0], Encode::FB_CROAK), -3)
' "$utf8_str")

để ngăn chặn việc xử lý dữ liệu không đúng định dạng.

Thí dụ:

perl -MEncode -CO -e '
  print substr(decode("UTF-8", $ARGV[0], Encode::FB_CROAK), -3)
' $'\xd0\xd2\xc9\xd7\xc5\xd4' # koi8-r привет

Xuất ra một cái gì đó như thế này:

utf8 "\xD0" does not map to Unicode at /usr/lib/x86_64-linux-gnu/perl/5.20/Encode.pm line 175.

Không phụ thuộc vào cài đặt ngôn ngữ (nghĩa là hoạt động với LC_ALL=C). Bash, sed, grep, awk, revĐòi hỏi một cái gì đó như thế này:LC_ALL=en_US.UTF-8

Giải pháp chung:

  • Nhận byte
  • Phát hiện mã hóa
  • Giải mã byte thành ký tự
  • Trích xuất charaсters
  • Mã hóa ký tự thành byte

Bạn có thể phát hiện mã hóa với uchardet . Xem thêm các dự án liên quan .

Bạn có thể giải mã / mã hóa bằng Encode trong Perl, codec trong Python 2.7

Ví dụ :

Trích xuất ba ký tự cuối cùng từ chuỗi utf-16le và chuyển đổi các ký tự này thành utf-8

utf16_le_str=$'\xff\xfe\x3f\x04\x40\x04\x38\x04\x32\x04\x35\x04\x42\x04' # привет

chardet <<<"$utf16_le_str"  # outputs <stdin>: UTF-16LE with confidence 1.0

last_three_utf8_chars=$(perl -MEncode -e '
    my $chars = decode("utf-16le", $ARGV[0]);
    my $last_three_chars = substr($chars, -3);
    my $bytes = encode("utf-8", $last_three_chars);
    print $bytes;
  ' "$utf16_le_str"
)

Xem thêm: perlunitut , Python 2 Unicode HOWTO


echolà nguồn chống đạn của bạn?
mikeerv

@mikeerv, decode/encodelà nguồn chống đạn của tôi. Làm sạch câu trả lời của tôi.
Evgeny Vereshchagin

Điều này cũng phụ thuộc vào cài đặt ngôn ngữ để đảm bảo rằng nó hoạt động chính xác, vì một tập hợp byte có thể phản ánh các ký tự khác nhau trong các bảng mã khác nhau. Nó "hoạt động" LC_ALL=Cvì đó là một cài đặt rất "ngớ ngẩn", nhưng nó có thể bị hỏng khi bạn cố chuyển một chuỗi UTF-8 sang SHIFT-5 hoặc chuỗi SHIFT-5 cho KOI8, v.v.
Martin Tournoij

@Carpetsmoker, cảm ơn. Bạn có thể giải thích bình luận của bạn? Tôi cho rằng perl -CAO -e 'print substr($ARGV[0], -3)'hoạt động tốt. Acác phần tử @ARGV dự kiến ​​sẽ là các chuỗi được mã hóa theo UTF-8, OSTDOUT sẽ ở dạng UTF-8.
Evgeny Vereshchagin

có vẻ như bạn kể về sự phân công choutf8_str
Evgeny Vereshchagin

1

Còn việc sử dụng "expr" hay "rev" thì sao?

Một câu trả lời tương tự như câu trả lời được cung cấp bởi @ G-Man : expr "$yourstring" : '.*\(...\)$' Nó có cùng một nhược điểm so với giải pháp grep.

Một mẹo nổi tiếng là kết hợp "cắt" với "rev": echo "$yourstring" | rev | cut -n 1-3 | rev


Các revgiải pháp trông rất giống jackman của glenn
Jeff Schaller

Bạn nói đúng @Jeff_Schaller: Tôi đã bỏ lỡ một cái của
glenn

0

Lấy kích thước của chuỗi với:

size=${#STRING}

Sau đó nhận chuỗi con của ký tự n cuối cùng:

echo ${STRING:size-n:size}

Ví dụ:

STRING=123456789
n=3
size=${#STRING}
echo ${STRING:size-n:size}

sẽ cho:

789

0

đuôi -n 1 phiên bản.log | awk '{chất nền in ($ 0, 0, chiều dài ($ 0) - (chiều dài ($ 0) -13))}'

Nếu bạn muốn in mười ba ký tự đầu tiên từ đầu


-1

printf sẽ không hoạt động nếu chuỗi có khoảng trắng trong đó.

Mã dưới đây cho chuỗi có không gian

str="Welcome to Linux"
echo -n $str | tail -c 3

nux


Ừm, nếu printfkhông làm việc, thì bạn đang làm điều gì đó rất sai.
Kusalananda

1
@Kusalananda: Dựa trên mệnh lệnh mà Saurabh thể hiện, họ đã thử printf $str(chứ không phải printf "$str"hoặc printf '%s' "$str"). Và, vâng, printf $strrất sai. ( echo -n $strkhông tốt hơn nhiều.)
G-Man nói 'Tái lập lại Monica'
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.