Perintah untuk mencetak hanya 3 karakter terakhir dari sebuah string

30

Saya tahu bahwa cutperintah dapat mencetak nkarakter pertama dari suatu string tetapi bagaimana cara memilih nkarakter terakhir ?

Jika saya memiliki string dengan jumlah karakter variabel, bagaimana saya bisa mencetak hanya tiga karakter terakhir dari string. misalnya.

Output "tidak terbatas" yang dibutuhkan adalah "ted"
"987654" output yang dibutuhkan adalah "654"
"123456789" output yang dibutuhkan adalah "789"
pengembaraan
sumber

Jawaban:

52

Mengapa tidak ada yang memberikan jawaban yang jelas?

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

... atau yang sedikit kurang jelas

grep -o '...$'

Diakui, yang kedua memiliki kelemahan yang garis dengan kurang dari tiga karakter menghilang; tetapi pertanyaannya tidak secara eksplisit mendefinisikan perilaku untuk kasus ini.

G-Man Mengatakan 'Reinstate Monica'
sumber
6
ataugrep -o '.\{3\}$'
Avinash Raj
3
atauecho "unlimited" | python -c "print raw_input()[-3:]"
Kiro
8
@ Kiro atau "echo unlimited" | java -jar EnterpriseWordTrimmer.jar, tapi saya tidak berpikir itu benar-benar perlu untuk membawa bahasa yang lebih berat untuk manipulasi karakter.
wchargin
11
@WChargin kamu lupajava -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}$'akan mencetak 3 karakter terakhir walaupun garis tersebut memiliki kurang dari 3 karakter. -Pmenghindari harus melarikan diri dari kawat gigi.
Raghu Dodda
43

Menjaga agar tetap sederhana - ekor

Kita tidak perlu ekspresi reguler, atau lebih dari satu proses, hanya untuk menghitung karakter.
Perintah tail, yang sering digunakan untuk menampilkan baris terakhir file, memiliki opsi -c( --bytes), yang tampaknya menjadi alat yang tepat untuk ini:

$ printf 123456789 | tail -c 3
789

(Ketika Anda berada di shell, masuk akal untuk menggunakan metode seperti dalam jawaban mikeserv, karena menyimpan memulai proses untuk tail.)

Karakter Unicode sungguhan?

Sekarang, Anda meminta tiga karakter terakhir ; Bukan itu yang diberikan jawaban ini: menghasilkan tiga byte terakhir !

Selama setiap karakter adalah satu byte, tail -chanya berfungsi. Jadi itu bisa digunakan jika set karakternya adalah ASCII, ISO 8859-1atau varian.

Jika Anda memiliki input Unicode, seperti dalam UTF-8format umum , hasilnya salah:

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

Dalam contoh ini, menggunakan UTF-8, karakter yunani alfa, beta dan gamma panjangnya dua byte:

$ printf 123αβγ | wc -c  
9

Opsi -msetidaknya dapat menghitung karakter unicode yang sebenarnya:

printf 123αβγ | wc -m
6

Ok, jadi 6 byte terakhir akan memberi kita 3 karakter terakhir:

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

Jadi, tailtidak mendukung penanganan karakter umum, dan bahkan tidak mencoba (lihat di bawah): Ini menangani garis ukuran variabel, tetapi tidak ada karakter ukuran variabel.

Mari kita begini: tailtepat untuk struktur masalah untuk dipecahkan, tetapi salah untuk jenis data.

GNU coreutils

Melihat lebih jauh, ternyata engkau coreutils GNU, koleksi alat dasar seperti sed, ls, taildan cut, belum sepenuhnya internasionalisasi. Yang terutama tentang mendukung Unicode.
Misalnya, cutakan menjadi kandidat yang baik untuk digunakan alih-alih mengikuti di sini untuk dukungan karakter; Itu memang memiliki opsi untuk bekerja pada byte atau karakter, -c( --bytes) dan -m( --chars);

Hanya itu -m/ --charsadalah, sebagai versi
cut (GNU coreutils) 8.212013,
tidak dilaksanakan!

Dari 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.


Lihat juga jawaban ini untuk Tidak dapat menggunakan `cut -c` (` --characters`) dengan UTF-8? .

Volker Siegel
sumber
2
Sebenarnya, sebagian besar jawaban lain tampaknya menangani Unicode dengan baik, selama lokal saat ini menentukan pengkodean UTF-8. cutTampaknya hanya solusi Anda dan glenn jackman yang berbasis itu.
Ilmari Karonen
@IlmariKaronen Benar, terima kasih atas petunjuknya. Saya telah mengedit, dengan beberapa detail tambahan.
Volker Siegel
1
Perhatikan bahwa POSIX secara eksplisit menentukan yang tailharus berurusan dengan byte, dan bukan karakter. Saya pernah membuat tambalan untuk menambahkan opsi baru untuk juga memilih karakter, tapi saya percaya itu tidak pernah digabung: - /
Martin Tournoij
Tidak bekerja dalam mode file, sepertitail -c3 -n10 /var/log/syslog
Suncatcher
@ Suncatcher saya mencoba, dan itu berhasil. Apa masalah yang Anda lihat? Perintah Anda tail -c3 -n10 /var/log/syslogmeminta 10 baris terakhir, dan itu berhasil untuk saya. Anda menggunakan opsi -c3, dan setelah itu opsi yang bertentangan -n10. Opsi selanjutnya diprioritaskan.
Volker Siegel
36

Jika teks Anda dalam variabel shell disebut STRING, Anda bisa melakukan ini dalam bash, zshatau mkshshell:

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

Atau

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

yang juga memiliki manfaat untuk bekerja dengan ksh93 dari mana sintaks itu berasal.

Intinya adalah bahwa :harus dipisahkan dari -, jika tidak menjadi ${var:-default}operator cangkang Bourne.

Sintaks yang setara dalam zshatau yashshell adalah:

printf '%s\n' "${STRING[-3,-1]}"
DopeGhoti
sumber
2
Apa jenis sintaksis / operasi yang dipanggil sehingga saya dapat mencari informasi lebih lanjut?
Tulains Córdova
6
Ini disebut Ekspansi Substring . Ini semacam Ekspansi Parameter . Bentuk umum adalah $ {parameter: offset: length} , tetapi bidang panjang adalah opsional (dan, seperti yang Anda lihat, itu telah dihilangkan dalam jawaban di atas). DopeGhoti bisa juga menulis ${STRING:(-3):3}(menentukan bidang panjang ), ${STRING: -3}(dengan spasi antara :dan -), atau ${STRING: -3:3}.
G-Man Mengatakan 'Reinstate Monica'
Dalam hal ini, menentukan panjang 3agak diperdebatkan karena meminta "tiga karakter dari ketiga dari karakter terakhir, inklusif" yang kebetulan merupakan operasi yang identik dalam istilah praktis untuk "Semua karakter ke depan dari yang ketiga dari yang terakhir , inklusif ".
DopeGhoti
13

Menggunakan awk:

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

Jika string ada dalam variabel yang bisa Anda lakukan:

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

Itu menghilangkan tiga karakter terakhir dari nilai $varlike:

${var%???}

... dan kemudian strip dari kepala $varsegalanya tetapi apa yang baru saja dilucuti seperti:

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

Metode ini memiliki kelebihan dan kekurangannya. Sisi baiknya adalah sepenuhnya POSIX-portable dan dapat digunakan di semua shell modern. Juga, jika $vartidak mengandung setidaknya tiga karakter tidak ada tetapi garis akhir trailing \ndicetak. Kemudian lagi, jika Anda ingin mencetaknya, Anda perlu langkah tambahan seperti:

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

Dengan cara $last3itu hanya akan kosong jika $varmengandung 3 byte atau lebih sedikit. Dan $varhanya diganti $last3jika $last3kosong atau unset- dan kita tahu itu bukan unsetkarena kita hanya mengaturnya.

mikeserv
sumber
+1 yang sangat rapi. Selain itu: alasan apa pun Anda tidak mengutip printfstring format Anda ?
jasonwryan
Mengapa tidak hanya menggunakan ${VARNAME:(-3)}(menganggap bash)?
DopeGhoti
1
Terima kasih telah mengklarifikasi; masuk akal, bahkan jika itu terlihat (bagi saya) sedikit aneh ...
jasonwryan
1
@DopeGhoti - hanya karena itu adalah asumsi yang hampir tidak pernah saya buat. Ini berfungsi seperti bashhalnya pada shell lain yang mengklaim kompatibilitas POSIX.
mikeserv
3
@odyssey - Masalahnya adalah cshadalah tidak di antara , modern POSIX-kompatibel kerang saya sebutkan di sini, sayangnya. Spec POSIX-shell dimodelkan setelah ksh, yang memodelkan dirinya sendiri setelah kombinasi keduanya cshdan shell gaya Bourne tradisional. kshmenggabungkan kedua cshfungsi kontrol pekerjaan yang sangat baik dan pengalihan i / o gaya Bourne lama. Itu juga menambahkan beberapa hal - seperti konsep manipulasi string yang saya tunjukkan di atas. Ini kemungkinan tidak akan berhasil dalam tradisionalcsh sejauh yang saya tahu, saya minta maaf untuk mengatakan.
mikeserv
7

Anda dapat melakukan ini, tetapi ini sedikit ... berlebihan:

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

Solusi anti peluru untuk utf-8 strings:

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")

Atau gunakan:

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

untuk mencegah penanganan data yang cacat.

Contoh:

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

Keluaran sesuatu seperti ini:

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

Tidak tergantung pada pengaturan lokal (yaitu bekerja dengan LC_ALL=C). Bash, sed, grep,awk , revMembutuhkan sesuatu seperti ini:LC_ALL=en_US.UTF-8

Solusi umum:

  • Terima byte
  • Deteksi penyandian
  • Dekode byte ke karakter
  • Ekstraksi karakter
  • Mengkodekan karakter ke byte

Anda dapat mendeteksi pengodean dengan uchardet . Lihat juga proyek terkait .

Anda dapat mendekode / menyandi dengan Encode di Perl, codec dengan Python 2.7

Contoh :

Ekstrak tiga karakter terakhir dari utf-16le string dan konversi karakter-karakter ini ke 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"
)

Lihat juga: perlunitut , Python 2 Unicode HOWTO

Evgeny Vereshchagin
sumber
echoApakah sumber antipeluru Anda?
mikeserv
@ mikeserv, decode/encodeadalah sumber antipeluru saya. Bersihkan jawaban saya.
Evgeny Vereshchagin
Ini juga tergantung pada pengaturan lokal untuk menjamin itu berfungsi dengan benar, karena satu set byte dapat mencerminkan karakter yang berbeda di rangkaian karakter yang berbeda. Ini "berfungsi" karena LC_ALL=Ckarena itu pengaturan yang sangat "bodoh", tetapi mungkin rusak ketika Anda mencoba untuk meneruskan string UTF-8 ke SHIFT-5, atau string SHIFT-5 ke KOI8, dll.
Martin Tournoij
@Carpetsmoker, terima kasih. Bisakah Anda menjelaskan komentar Anda? Saya kira itu perl -CAO -e 'print substr($ARGV[0], -3)'berfungsi dengan baik. Aelemen @ARGV diharapkan berupa string yang dikodekan dalam UTF-8, OSTDOUT akan berada di UTF-8.
Evgeny Vereshchagin
Sepertinya Anda memberi tahu tentang penugasan keutf8_str
Evgeny Vereshchagin
1

Bagaimana dengan menggunakan "expr" atau "rev"?

Jawaban serupa dengan yang diberikan oleh @ G-Man :expr "$yourstring" : '.*\(...\)$' Ini memiliki kelemahan yang sama dari solusi grep.

Trik yang terkenal adalah menggabungkan "potong" dengan "putaran": echo "$yourstring" | rev | cut -n 1-3 | rev

Gildux
sumber
The revsolusi terlihat banyak seperti glenn jackman ini
Jeff Schaller
Anda benar @Jeff_Schaller: Saya melewatkan satu glenn :-(
gildux
0

Dapatkan ukuran string dengan:

size=${#STRING}

Kemudian dapatkan substring dari n karakter terakhir:

echo ${STRING:size-n:size}

Sebagai contoh:

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

akan memberi:

789
Esref
sumber
0

tail -n 1 revisi.log | awk '{print substr ($ 0, 0, length ($ 0) - (length ($ 0) -13))}'

Jika Anda ingin mencetak tiga belas karakter pertama dari awal

Ankit Vishwakarma
sumber
-1

printf tidak akan berfungsi jika string memiliki spasi di dalamnya.

Kode di bawah ini untuk string dengan spasi

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

nux

Saurabh
sumber
Um, jika printftidak berhasil, maka Anda melakukan sesuatu yang sangat salah.
Kusalananda
1
@ Kusalananda: Berdasarkan pada perintah yang ditunjukkan Saurabh, mereka mencoba printf $str(bukan printf "$str"atau printf '%s' "$str"). Dan, ya, printf $stradalah sangat salah. ( echo -n $strtidak jauh lebih baik.)
G-Man Mengatakan 'Reinstate Monica'