ลบอักขระตัวสุดท้ายของสตริงโดยใช้การจัดการสตริงในเชลล์สคริปต์


187

ฉันต้องการลบอักขระตัวสุดท้ายของสตริงฉันลองใช้สคริปต์ตัวน้อยนี้:

#! /bin/sh 

t="lkj"
t=${t:-2}
echo $t

แต่มันพิมพ์ "lkj" สิ่งที่ฉันทำผิด?

คำตอบ:


115

ในเปลือก POSIX ไวยากรณ์${t:-2}หมายถึงบางสิ่งบางอย่างที่แตกต่างกัน - มันจะขยายมูลค่าของtถ้าtมีการตั้งค่าและไม่โมฆะและอื่น ๆ 2ค่า หากต้องการตัดอักขระเดี่ยวตามการขยายพารามิเตอร์ไวยากรณ์ที่คุณอาจต้องการคือ${t%?}

โปรดสังเกตว่าในksh93, bashหรือzsh, ${t:(-2)}หรือ${t: -2}(หมายเหตุพื้นที่) มีกฎหมายการขยายตัวย่อย แต่อาจจะไม่ใช่สิ่งที่คุณต้องการตั้งแต่พวกเขากลับมาย่อยเริ่มต้นที่ตำแหน่ง 2 ตัวละครในจากปลาย (คือมันเอาครั้งแรกที่ตัวละครiของ สตริงijk)

ดูส่วนการขยายพารามิเตอร์เชลล์ของคู่มืออ้างอิง Bash สำหรับข้อมูลเพิ่มเติม:


4
คุณสนใจที่จะอธิบายว่าอะไรคือเวทมนต์ที่อยู่เบื้องหลัง '%' ?
afraisse

8
@afraisse ${parameter%word}ลบการจับคู่รูปแบบคำต่อท้ายที่สั้นที่สุดword- ดูส่วนการขยายพารามิเตอร์ของman bash
steeldriver

3
วิธีนี้ใช้งานได้ดีสำหรับ Bash 4.1.2: $ {t%?} สำหรับผู้ที่ติดอยู่กับ CentOS / RHEL 6.x
Joey T

185

ด้วยbash4.2 ขึ้นไปคุณสามารถทำสิ่งต่อไปนี้

${var::-1}

ตัวอย่าง:

$ a=123
$ echo "${a::-1}"
12

โปรดสังเกตว่าสำหรับรุ่นเก่าbash(เช่นbash 3.2.5ใน OS X) คุณควรเว้นช่องว่างระหว่างและหลังเครื่องหมายโคลอน:

${var: : -1}

13
สิ่งนี้ใช้ได้กับbashเวอร์ชัน 4.2-alpha และสูงกว่าแย่กว่าที่ฉันมีในรุ่นก่อนหน้านี้ : - /
hjk

2
@iamaziz: จากการทุบตีการเปลี่ยนแปลงที่มีความยาวเชิงลบถูกเพิ่มเฉพาะใน${var:offset:lenght} bash 4.2บางที OSX bashเพิ่มแพทช์ของตัวเองสำหรับ
cuonglm

1
@cuonglm ไม่ทำงาน: /
iamaziz

1
ใช้งานไม่ได้กับ mac
shinzou

1
MACsters มองลงไปที่คำตอบของรัส
P ฉัน

67

สำหรับการลบnอักขระสุดท้ายออกจากบรรทัดที่ไม่ใช้sedOR awk:

> echo lkj | rev | cut -c (n+1)- | rev

ตัวอย่างเช่นคุณสามารถลบอักขระตัวสุดท้ายone characterโดยใช้สิ่งนี้:

> echo lkj | rev | cut -c 2- | rev

> lk

จากrevmanpage:

DESCRIPTION
ยูทิลิตี้ rev จะคัดลอกไฟล์ที่ระบุไปยังเอาต์พุตมาตรฐานโดยกลับลำดับของอักขระในทุกบรรทัด หากไม่มีไฟล์ที่ระบุอินพุตมาตรฐานจะถูกอ่าน

UPDATE:

หากคุณไม่ทราบความยาวของสตริงลอง:

$ x="lkj"
$ echo "${x%?}"
lk

62

ใช้ sed มันควรจะเร็วเท่า

sed 's/.$//'

เดียวของคุณก้องecho ljk | sed 's/.$//'เป็นแล้ว
เมื่อใช้สิ่งนี้สตริง 1 บรรทัดอาจมีขนาดใดก็ได้


10
ทราบว่าในกรณีทั่วไปก็ไม่ได้ลบตัวอักษรตัวสุดท้ายของสตริงแต่ตัวอักษรตัวสุดท้ายของสายของสตริงทุก
Stéphane Chazelas

44

ตัวเลือกบางอย่างขึ้นอยู่กับเปลือก:

  • POSIX: t=${t%?}
  • บอร์น: t=`expr " $t" : ' \(.*\).'`
  • zsh / Yash: t=${t[1,-2]}
  • ทุบตี / zsh: t=${t:0:-1}
  • ksh93 / ทุบตี / zsh / mksh: t=${t:0:${#t}-1}
  • ksh93 / ทุบตี / zsh / mksh: t=${t/%?}
  • ksh93: t=${t/~(E).$/}
  • ES: @ {t=$1} ~~ $t *?

โปรดทราบว่าในขณะที่ทุกคนควรจะดึงตัวอักษรตัวสุดท้ายออกมาคุณจะพบว่าการใช้งานบางอย่าง (ที่ไม่รองรับตัวอักษรหลายไบต์) ดึงไบต์สุดท้ายแทน (ดังนั้นน่าจะทำให้ตัวอักษรตัวสุดท้ายเสีย )

ตัวexprแปรผันถือว่า$tไม่ได้ลงท้ายด้วยอักขระขึ้นบรรทัดใหม่มากกว่าหนึ่งตัว นอกจากนี้ยังจะกลับมาเป็นสถานะทางออกที่ไม่ใช่ศูนย์ถ้าสตริงส่งผลให้สิ้นสุดขึ้นเป็น0(หรือ000หรือแม้กระทั่ง-0กับการใช้งานบางส่วน) นอกจากนี้ยังอาจให้ผลลัพธ์ที่ไม่คาดคิดหากสตริงมีอักขระที่ไม่ถูกต้อง


ดีและทั่วถึง! แต่ ... ฉันคิดว่าเชลล์ทั้งหมดนั้นรองรับ POSIX ดังนั้นทุกคนควรใช้อันนั้นเป็นแบบพกพามากที่สุด นับตัวละครน้อยที่สุดด้วย!
Russ

@Russ t=${t%?}ไม่ใช่ Bourne แต่คุณไม่น่าจะเจอกับ Bourne shell ในปัจจุบัน ${t%?}ทำงานในอื่น ๆ ทั้งหมดแม้ว่า
Stéphane Chazelas

ไม่มีตัวเลือกสำหรับตู้ปลา! น่าจะเป็นที่นิยมมากขึ้นในทุกวันนี้กว่า ksh93 ...
rien333

@ rien333 ฉันจะรอให้อินเทอร์เฟซเสถียรเล็กน้อย fishกำลังทำงาน 2.3.0 ซึ่งนำเสนอstringบิลด์อินนั้นไม่ได้ถูกปล่อยออกมาในช่วงเวลาของคำถามและคำตอบ ด้วยเวอร์ชันที่ฉันกำลังทดสอบคุณจำเป็นต้องstring replace -r '(?s).\z' '' -- $t(และฉันคาดหวังว่าพวกเขาต้องการเปลี่ยนสิ่งนั้นพวกเขาควรเปลี่ยนการตั้งค่าสถานะที่ส่งผ่านไปยัง PCRE) หรือที่ซับซ้อนกว่า มันเกี่ยวข้องกับตัวละครขึ้นบรรทัดใหม่ไม่ดีและฉันรู้ว่าพวกเขากำลังวางแผนที่จะเปลี่ยนแปลงสิ่งนั้นเช่นกัน
Stéphane Chazelas

โหวตขึ้นสำหรับคำตอบ POSIX ยืนยันการทำงานกับ Bash 3.2.57 (1)
Avindra Goolcharan

26

คำตอบที่พกพาได้ง่ายที่สุดและสั้นที่สุดคือ:

${t%?}

ใช้งานได้กับ bash, sh, ash, dash, busybox / ash, zsh, ksh ฯลฯ

มันทำงานได้โดยใช้การขยายพารามิเตอร์ของเชลล์วัยเรียน โดยเฉพาะการ%ระบุที่จะลบคำต่อท้ายการจับคู่ที่เล็กที่สุดของพารามิเตอร์tที่ตรงกับรูปแบบ glob ?(เช่น: ตัวละครใด ๆ )

ดู "ลบรูปแบบคำต่อท้ายที่เล็กที่สุด" ที่นี่สำหรับคำอธิบายโดยละเอียด (มาก) และพื้นหลังเพิ่มเติม ดูเอกสารสำหรับเชลล์ของคุณ (เช่น:) man bashใต้ "การขยายพารามิเตอร์"


หมายเหตุด้านข้างหากคุณต้องการลบอักขระตัวแรกแทนคุณจะต้องใช้${t#?}เนื่องจากการ#จับคู่จากด้านหน้าของสตริง (คำนำหน้า) แทนด้านหลัง (ส่วนต่อท้าย)

นอกจากนี้ยังมีข้อสังเกตว่าทั้งสอง%และ#มี%%และ##รุ่นซึ่งตรงกับเวอร์ชันที่ยาวที่สุดของรูปแบบที่กำหนดแทนที่จะสั้นที่สุด ทั้งสอง${t%%?}และ${t##?}จะทำเช่นเดียวกับตัวดำเนินการเดียวในกรณีนี้ (ดังนั้นอย่าเพิ่มอักขระพิเศษที่ไร้ประโยชน์) นี่เป็นเพราะ?รูปแบบที่กำหนดให้ตรงกับอักขระเดียวเท่านั้น ผสมใน*กับบางส่วนที่ไม่ใช่สัญลักษณ์และสิ่งที่ได้รับความน่าสนใจมากขึ้นด้วยและ%%##

การทำความเข้าใจเกี่ยวกับการขยายพารามิเตอร์หรืออย่างน้อยก็รู้เกี่ยวกับการมีอยู่ของพวกเขาและรู้วิธีที่จะมองหามันมีประโยชน์อย่างเหลือเชื่อสำหรับการเขียนและถอดรหัสเชลล์สคริปต์ในหลาย ๆ รสชาติ การขยายพารามิเตอร์มักจะดูเหมือนว่าอาร์เคนเชลล์วูดูสำหรับหลาย ๆ คนเพราะ ... ดี ... พวกมันเป็นอาร์ซีเอของวูดู (แม้ว่าจะค่อนข้างดีถ้าคุณรู้ว่าจะมองหา แม้ว่าจะติดตั้งไว้ในเข็มขัดแล้วก็ดี


สั้นและหวานและใช้งานได้ทั้ง MacOS และ Linux!
dbernard

18
t=lkj
echo ${t:0:${#t}-1}

คุณได้รับสตริงย่อยจาก 0 ถึงความยาวสตริง -1 อย่างไรก็ตามโปรดทราบว่าการลบย่อยนี้เป็นการทุบตีที่เฉพาะเจาะจงและจะไม่ทำงานกับเชลล์อื่น ๆ

ตัวอย่างเช่นdashไม่สามารถแยกวิเคราะห์ได้

echo ${t:0:$(expr ${#t} - 1)}

ตัวอย่างเช่นบน Ubuntu /bin/shคือdash


15

คุณสามารถใช้headเพื่อพิมพ์ทั้งหมดยกเว้นตัวอักษรตัวสุดท้าย

$ s='i am a string'
$ news=$(echo -n $s | head -c -1)
$ echo $news
i am a strin

แต่น่าเสียดายที่บางรุ่นheadไม่มี-ตัวเลือกนำหน้า นี่เป็นกรณีheadที่มาพร้อมกับ OS X



5

การปรับแต่งบางอย่าง หากต้องการลบอักขระมากกว่าหนึ่งตัวคุณสามารถเพิ่มเครื่องหมายคำถามได้หลายเครื่องหมาย ตัวอย่างเช่นหากต้องการลบอักขระสองตัวสุดท้ายออกจากตัวแปร: $SRC_IP_MSGคุณสามารถใช้:

SRC_IP_MSG=${SRC_IP_MSG%??}

4

เพียงเพื่อทำให้การใช้ bash บริสุทธิ์ที่เป็นไปได้สมบูรณ์:

#!/bin/bash

# Testing substring removal
STR="Exemple string with trailing whitespace "
echo "'$STR'"
echo "Removed trailing whitespace: '${STR:0:${#STR}-1}'"
echo "Removed trailing whitespace: '${STR/%\ /}'"

ไวยากรณ์แรกใช้สตริงย่อยจากสตริงไวยากรณ์คือ สำหรับอันที่สองให้สังเกตเครื่องหมายซึ่งหมายความว่า 'จากจุดสิ้นสุดของบรรทัด' และไวยากรณ์คือ
${STRING:OFFSET:LENGTH}
%
${STRING/PATTERN/SUBSTITUTION}

และนี่คือรูปแบบสั้น ๆ สองรูปแบบที่กล่าวมาข้างต้น

echo "Removed trailing whitespace: '${STR::-1}'"
echo "Removed trailing whitespace: '${STR%\ }'"

สังเกตที่นี่อีกครั้ง%สัญญาณหมายถึง 'ลบ (นั่นคือแทนที่ด้วย' ') รูปแบบที่ตรงกันสั้นที่สุด (ที่นี่แทนด้วยช่องว่างที่หลบหนี' \ 'จากจุดสิ้นสุดของพารามิเตอร์ - นี่ชื่อSTR


1

ในขณะที่เรายังสามารถใช้phpในบรรทัดคำสั่งหรือสคริปต์เชลล์ บางครั้งมันมีประโยชน์สำหรับการแยกวิเคราะห์การผ่าตัด

php -r "echo substr('Hello', 0, -1);" 
// Output hell

ด้วยท่อ:

echo "hello" | php -r "echo substr(trim(fgets(STDIN)), 0, -1);"
// Output hell

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.