คำสั่งพิมพ์เฉพาะอักขระ 3 ตัวสุดท้ายของสตริง


30

ฉันรู้ว่าcutคำสั่งสามารถพิมพ์nอักขระตัวแรกของสตริง แต่จะเลือกnอักขระตัวสุดท้ายได้อย่างไร?

หากฉันมีสตริงที่มีจำนวนอักขระผันแปรฉันจะพิมพ์อักขระสามตัวสุดท้ายของสตริงได้อย่างไร เช่น.

เอาต์พุต "ไม่ จำกัด " ที่ต้องการคือ "ted"
จำเป็นต้องใช้เอาต์พุต "987654" คือ "654"
จำเป็นต้องใช้เอาต์พุต "123456789" คือ "789"

คำตอบ:


52

ทำไมไม่มีใครให้คำตอบที่ชัดเจน?

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

... หรือชัดเจนน้อยกว่าเล็กน้อย

grep -o '...$'

เป็นที่ยอมรับคนที่สองมีข้อเสียเปรียบที่มีอักขระน้อยกว่าสามตัวหายไป แต่คำถามไม่ได้กำหนดพฤติกรรมอย่างชัดเจนสำหรับกรณีนี้


6
หรือgrep -o '.\{3\}$'
Avinash Raj

3
หรือecho "unlimited" | python -c "print raw_input()[-3:]"
Kiro

8
@Kiro หรือ"echo unlimited" | java -jar EnterpriseWordTrimmer.jarแต่ฉันไม่คิดว่ามันจำเป็นจริงๆที่จะต้องใช้ภาษาที่หนักกว่าสำหรับการจัดการกับตัวละคร
wchargin

11
@WChargin คุณลืมjava -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}$'จะพิมพ์อักขระ 3 ตัวสุดท้ายแม้ว่าบรรทัดจะมีน้อยกว่า 3 ตัว -Pหลีกเลี่ยงการหลีกเลี่ยงการจัดฟัน
Raghu Dodda

43

ทำให้มันง่าย - หาง

เราไม่ควรต้องการนิพจน์ทั่วไปหรือมากกว่าหนึ่งโพรเซสเพื่อนับจำนวนตัวอักษร
คำสั่งtailมักใช้เพื่อแสดงบรรทัดสุดท้ายของไฟล์มีตัวเลือก-c( --bytes) ซึ่งดูเหมือนจะเป็นเครื่องมือที่เหมาะสมสำหรับสิ่งนี้:

$ printf 123456789 | tail -c 3
789

(เมื่อคุณอยู่ในเชลล์คุณควรใช้วิธีการเช่นเดียวกับคำตอบของ mikeserv เพราะจะช่วยประหยัดการเริ่มต้นกระบวนการสำหรับtail)

อักขระ Unicode จริง

ตอนนี้คุณขอตัวละครสามตัวสุดท้าย; นั่นไม่ใช่สิ่งที่คำตอบนี้ให้คุณ: มันแสดงผลสามไบต์ล่าสุด!

ตราบใดที่อักขระแต่ละตัวมีหนึ่งไบต์tail -cก็ใช้งานได้ ดังนั้นจึงสามารถนำมาใช้ถ้าตั้งค่าตัวอักษรเป็นASCII,ISO 8859-1หรือตัวแปร

หากคุณมีอินพุต Unicode เช่นเดียวกับในUTF-8รูปแบบทั่วไปผลลัพธ์จะผิด:

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

ในตัวอย่างนี้การใช้UTF-8อักขระกรีกอัลฟาเบต้าและแกมม่ามีความยาวสองไบต์:

$ printf 123αβγ | wc -c  
9

ตัวเลือก-mอย่างน้อยสามารถนับอักขระ Unicode จริง:

printf 123αβγ | wc -m
6

ตกลงดังนั้น 6 ไบต์สุดท้ายจะให้อักขระ 3 ตัวสุดท้าย:

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

ดังนั้นจึงtailไม่รองรับการจัดการตัวอักษรทั่วไปและไม่ได้ลอง (ดูด้านล่าง): มันจัดการกับเส้นขนาดตัวแปร แต่ไม่มีตัวอักษรขนาดตัวแปร

ลองคิดแบบนี้: tailมันเหมาะกับโครงสร้างของปัญหาที่จะแก้ไข แต่มันผิดสำหรับข้อมูล

coreutils ของ GNU

มองต่อไปก็ปรากฎว่าเจ้า coreutils GNU คอลเลกชันของเครื่องมือพื้นฐานที่ชอบsed, ls, tailและcutจะยังไม่สากลอย่างเต็มที่ ซึ่งส่วนใหญ่เกี่ยวกับการสนับสนุน Unicode
ตัวอย่างเช่นcutจะเป็นผู้สมัครที่ดีที่จะใช้แทนหางที่นี่เพื่อรองรับตัวละคร; มันมีตัวเลือกสำหรับการทำงานกับไบต์หรือตัวอักษร-c( --bytes) และ-m(--chars );

เฉพาะที่เป็น-m/ --charsเป็นรุ่น
cut (GNU coreutils) 8.212013
ไม่ได้ดำเนินการ!

จาก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.


ดูเพิ่มเติมที่คำตอบนี้เพื่อไม่สามารถใช้ `cut -c` (` --characters`) กับ UTF-8 ได้? .


2
ที่จริงแล้วคำตอบอื่น ๆ ส่วนใหญ่ดูเหมือนว่าจะจัดการ Unicode ได้ดีตราบใดที่ภาษาปัจจุบันระบุการเข้ารหัส UTF-8 cutดูเหมือนว่าจะมีเพียงโซลูชันของคุณและเกล็นแจ็คแมนเท่านั้น
Ilmari Karonen

@IlmariKaronen True ขอบคุณสำหรับคำใบ้ ฉันได้แก้ไขโดยมีรายละเอียดเพิ่มเติม
Volker Siegel

1
โปรดทราบว่า POSIX ระบุอย่างชัดเจนว่าtailควรจัดการกับไบต์และไม่ใช่ตัวอักษร ฉันเคยทำการปะเพื่อเพิ่มตัวเลือกใหม่เพื่อเลือกอักขระ แต่ฉันเชื่อว่าไม่เคยถูกผสาน: - /
Martin Tournoij

ไม่ทำงานในโหมดไฟล์เช่นtail -c3 -n10 /var/log/syslog
Suncatcher

@Suncatcher ฉันลองแล้วมันใช้งานได้ คุณเห็นปัญหาอะไร คำสั่งของคุณtail -c3 -n10 /var/log/syslogขอ 10 บรรทัดสุดท้ายและนั่นก็ใช้ได้กับฉัน คุณใช้ตัวเลือก-c3และหลังจากนั้นตัวเลือกที่ขัดแย้ง-n10กัน ตัวเลือกในภายหลังมีความสำคัญ
Volker Siegel

36

ถ้าข้อความของคุณอยู่ในตัวแปรเปลือกที่เรียกว่าSTRINGคุณสามารถทำเช่นนี้ในbash, zshหรือmkshเปลือก:

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

หรือ

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

ซึ่งยังมีประโยชน์ในการทำงานกับ ksh93 โดยที่ไวยากรณ์นั้นมาจาก

ประเด็นก็คือว่า:จะต้องมีการแยกออกจาก-มิฉะนั้นมันจะกลายเป็น${var:-default}ผู้ประกอบการของเชลล์เป้าหมาย

ไวยากรณ์เทียบเท่าในzshหรือyashเปลือกคือ:

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

2
ไวยากรณ์ / การดำเนินการชนิดนั้นเรียกว่าอะไรดังนั้นฉันสามารถค้นหาข้อมูลเพิ่มเติมได้
Tulains Córdova

6
มันเรียกว่าการขยายตัว Substring มันเป็นชนิดของการขยายตัวพารามิเตอร์ รูปแบบทั่วไปคือ$ {พารามิเตอร์: offset: length}แต่ฟิลด์ความยาวเป็นตัวเลือก (และอย่างที่คุณเห็นมันถูกละไว้ในคำตอบข้างต้น) DopeGhoti ยังอาจได้เขียน${STRING:(-3):3}(ระบุความยาวของสนาม) ${STRING: -3}(ที่มีช่องว่างระหว่างการ:และ-) ${STRING: -3:3}หรือ
G-Man กล่าวว่า 'Reinstate Monica'

ในกรณีนี้การระบุความยาวของ3ค่อนข้างเป็นที่สงสัยว่า "สามตัวละครจากที่สามจากตัวละครสุดท้ายรวม" ซึ่งเกิดขึ้นจะเป็นการดำเนินการที่เหมือนกันในแง่การปฏิบัติเพื่อ "ตัวละครทั้งหมดจากที่สามจากที่ผ่านมา รวมอยู่ด้วย ".
DopeGhoti


11

หากสตริงอยู่ในตัวแปรคุณสามารถทำได้:

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

ที่แถบอักขระสามตัวสุดท้ายจากค่า$varlike:

${var%???}

... และจากนั้นแยกออกจากหัวของ$varทุกสิ่งแต่สิ่งที่เพิ่งถูกปล้นเช่น:

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

วิธีนี้มีคว่ำและลง ในด้านสว่างมันเป็นแบบพกพา POSIX อย่างเต็มที่และควรทำงานในเปลือกที่ทันสมัย นอกจากนี้หาก$varไม่มีอย่างน้อยสามตัวอักษรไม่มีอะไรนอกจาก\newline ต่อท้ายจะถูกพิมพ์ จากนั้นอีกครั้งหากคุณต้องการพิมพ์ในกรณีนั้นคุณต้องมีขั้นตอนเพิ่มเติมเช่น:

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

ด้วยวิธี$last3นี้จะว่างเปล่าถ้า$varมี 3 หรือน้อยกว่าไบต์ และ$varจะถูกแทนที่ด้วย$last3ถ้า$last3ว่างเปล่าหรือunset- และเรารู้ว่าไม่ใช่unsetเพราะเราเพิ่งตั้ง


+1 ค่อนข้างเป็นระเบียบเรียบร้อย นอกเหนือ: ด้วยเหตุผลใดก็ตามที่คุณไม่ได้อ้างprintfสตริงรูปแบบของคุณ?
jasonwryan

ทำไมไม่ใช้${VARNAME:(-3)}(สันนิษฐานbash)?
DopeGhoti

1
ขอบคุณสำหรับการชี้แจง ทำให้รู้สึกถึงแม้ว่ามันจะมีลักษณะ (ให้ฉัน) เล็ก ๆ น้อย ๆ ที่แปลก ...
jasonwryan

1
@DopeGhoti - เพียงเพราะเป็นข้อสันนิษฐานที่ฉันแทบไม่เคยทำ วิธีนี้ใช้ได้ผลเช่นเดียวbashกับในเชลล์อื่น ๆ ที่อ้างว่าใช้งานร่วมกันได้ของ POSIX
mikeserv

3
@odyssey - ปัญหาคือcshจะไม่ได้อยู่ในหมู่ที่ทันสมัย POSIX ได้เปลือกหอยที่ฉันพูดถึงที่นี่โชคไม่ดี ข้อมูลจำเพาะเชลล์ POSIX ถูกสร้างแบบจำลองkshซึ่งทำโมเดลตัวเองหลังจากการรวมกันของทั้งสองcshและเชลล์สไตล์ Bourne แบบดั้งเดิม kshรวมcshฟังก์ชันการควบคุมงานที่ยอดเยี่ยมของทั้งสองอย่างและการเปลี่ยนเส้นทาง i / o สไตล์ Bourne เก่า นอกจากนี้ยังเพิ่มบางสิ่งเช่นแนวคิดการจัดการสตริงที่ฉันแสดงไว้ด้านบน สิ่งนี้จะไม่ทำงานในแบบดั้งเดิมใด ๆcshเท่าที่ฉันรู้ฉันขอโทษที่จะพูด
mikeserv


3

วิธีแก้ปัญหากระสุนสำหรับสตริง 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")

หรือใช้:

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

เพื่อป้องกันการจัดการข้อมูลที่ผิดรูปแบบ

ตัวอย่าง:

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

แสดงผลแบบนี้:

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

ไม่ได้ขึ้นอยู่กับการตั้งค่าภาษา (เช่นใช้งานได้LC_ALL=C) Bash, sed, grep, awk, revต้องมีบางอย่างเช่นนี้LC_ALL=en_US.UTF-8

วิธีแก้ปัญหาทั่วไป:

  • รับจำนวนไบต์
  • ตรวจจับการเข้ารหัส
  • ถอดรหัสไบต์เป็นอักขระ
  • แยกตัวอักษร
  • เข้ารหัสอักขระเป็นไบต์

คุณสามารถตรวจสอบการเข้ารหัสด้วยuchardet ดูโครงการที่เกี่ยวข้องด้วย

คุณสามารถถอดรหัส / เข้ารหัสด้วยEncodeใน Perl, ตัวแปลงสัญญาณใน Python 2.7

ตัวอย่าง :

แยกอักขระสามตัวสุดท้ายจากสตริง utf-16le และแปลงอักขระเหล่านี้เป็น 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"
)

ดูเพิ่มเติมที่: perlunitut , Python 2 Unicode HOWTO


echoเป็นแหล่งกระสุนของคุณหรือไม่
mikeserv

@mikeserv decode/encodeเป็นแหล่งกระสุนของฉัน ทำความสะอาดคำตอบของฉัน
Evgeny Vereshchagin

สิ่งนี้ยังขึ้นอยู่กับการตั้งค่าภาษาเพื่อรับประกันว่าทำงานได้อย่างถูกต้องเนื่องจากชุดของไบต์อาจแสดงถึงอักขระที่แตกต่างกันในชุดอักขระที่แตกต่างกัน มัน "ใช้งานได้" LC_ALL=Cเพราะมีการตั้งค่าที่ "โง่มาก" แต่อาจแตกเมื่อคุณพยายามส่งสตริง UTF-8 ไปที่ SHIFT-5 หรือสตริง SHIFT-5 ถึง KOI8 เป็นต้น
Martin Tournoij

@Carpetsmoker ขอบคุณ คุณสามารถอธิบายความคิดเห็นของคุณได้ไหม ฉันคิดว่ามันperl -CAO -e 'print substr($ARGV[0], -3)'ใช้ได้ดี Aองค์ประกอบ @ARGV คาดว่าจะเป็นสตริงที่เข้ารหัสใน UTF-8, OSTDOUT จะเป็น UTF-8
Evgeny Vereshchagin

ดูเหมือนว่าคุณจะบอกเกี่ยวกับการมอบหมายให้utf8_str
Evgeny Vereshchagin

1

แล้วการใช้ "expr" หรือ "rev" ล่ะ

คำตอบที่คล้ายกับที่ได้รับจาก @ G-Man : expr "$yourstring" : '.*\(...\)$' มันมีข้อด้อยเหมือนกันกับโซลูชัน grep

เคล็ดลับที่รู้จักกันดีคือการรวม "ตัด" กับ "rev": echo "$yourstring" | rev | cut -n 1-3 | rev


revวิธีการแก้ปัญหาดูเหมือนมากของเกล็นแจ็คแมน
เจฟฟ์ชาลเลอร์

คุณพูดถูก @Jeff_Schaller: ฉันพลาดหนึ่งของ glenn :-(
gildux

0

รับขนาดของสตริงด้วย:

size=${#STRING}

จากนั้นรับค่าสตริงย่อยของอักขระ n ตัวสุดท้าย:

echo ${STRING:size-n:size}

ตัวอย่างเช่น:

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

จะให้:

789

0

tail -n 1 revisions.log | awk '{print substr ($ 0, 0, ความยาว ($ 0) - (ความยาว ($ 0) -13))}'

หากคุณต้องการพิมพ์อักขระสิบสามตัวแรกจากจุดเริ่มต้น


-1

printf จะไม่ทำงานหากสตริงมีช่องว่างอยู่

โค้ดด้านล่างสำหรับสตริงที่มีช่องว่าง

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

Nux


อืมถ้าprintfไม่ทำงานแล้วคุณจะทำอะไรบางอย่างมากผิดปกติ
Kusalananda

1
@Kusalananda: ขึ้นอยู่กับคำสั่งที่ Saurabh แสดงให้เห็นว่าพวกเขาพยายามprintf $str(มากกว่าprintf "$str"หรือprintf '%s' "$str") และใช่printf $strเป็นอย่างมากที่ไม่ถูกต้อง ( echo -n $strไม่ดีไปกว่านี้)
G-Man พูดว่า 'Reinstate Monica'
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.