จำนวนอักขระในเอาต์พุตของคำสั่งเชลล์


12

ฉันกำลังเขียนบทซึ่งความต้องการในการคำนวณจำนวนตัวอักษรในการส่งออกคำสั่งในขั้นตอนเดียว

ตัวอย่างเช่นการใช้คำสั่งreadlink -f /etc/fstabควรส่งคืน10เนื่องจากผลลัพธ์ของคำสั่งนั้นมีความยาว 10 อักขระ

สิ่งนี้สามารถเกิดขึ้นได้กับตัวแปรที่เก็บไว้โดยใช้รหัสต่อไปนี้:

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

น่าเสียดายที่การใช้สูตรเดียวกันกับสตริงที่สร้างคำสั่งไม่ทำงาน:

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

ฉันเข้าใจว่าเป็นไปได้ที่จะทำสิ่งนี้โดยการบันทึกผลลัพธ์เป็นตัวแปรแรก:

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

แต่ฉันต้องการลบขั้นตอนพิเศษ

เป็นไปได้ไหม ความเข้ากันได้กับ Almquist shell (sh) โดยใช้เฉพาะยูทิลิตี้ที่อยู่ในตัวหรือมาตรฐานเท่านั้น


1
ผลลัพธ์ของreadlink -f /etc/fstabคือ11ตัวอักษร อย่าลืมบรรทัดใหม่ มิฉะนั้นคุณจะเห็น/etc/fstabluser@cern:~$ เมื่อคุณวิ่งจากเปลือก
Phil

@PhilFrost คุณดูเหมือนจะมีพรอมต์ตลกคุณทำงานใน CERN หรือไม่?
Dmitry Grigoryev

คำตอบ:


9

ด้วยGNU expr :

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

+มีคุณลักษณะพิเศษของ GNU exprเพื่อให้แน่ใจว่าการโต้แย้งต่อไปคือการได้รับการปฏิบัติเป็นสตริงแม้ว่ามันจะเกิดขึ้นจะเป็นexprผู้ประกอบการเช่นmatch, length, +...

ด้านบนจะตัดบรรทัดใหม่ของเอาต์พุตต่อท้าย ในการหลีกเลี่ยง:

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

ผลลัพธ์ถูกลบออกเป็น2เพราะขึ้นบรรทัดใหม่สุดท้ายreadlinkและตัวละครที่.เราเพิ่มเข้าไป

ด้วยสตริง Unicode exprดูเหมือนจะไม่ทำงานเพราะจะส่งคืนความยาวของสตริงเป็นไบต์แทนที่จะนับจำนวนอักขระ (ดูบรรทัดที่ 654 )

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

ดังนั้นคุณสามารถใช้:

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

POSIXLY:

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

ช่องว่างก่อนการแทนที่คำสั่งป้องกันคำสั่งไม่ให้ทำงานล้มเหลวด้วยสตริงที่ขึ้นต้นด้วย-ดังนั้นเราต้องลบ 3


ขอบคุณ! ดูเหมือนว่าตัวอย่างที่สามของคุณจะทำงานได้โดยไม่ต้องใช้LC_ALL=C.UTF-8ซึ่งจะทำให้สิ่งต่าง ๆ ง่ายขึ้นอย่างมากหากการเข้ารหัสของสตริงจะไม่เป็นที่รู้จักล่วงหน้า
user339676

2
expr length $(echo "*")- ไม่ อย่างน้อยใช้เครื่องหมายคำพูดคู่: expr length "$(…)". แต่สิ่งนี้จะตัดการขึ้นบรรทัดใหม่ต่อท้ายจากคำสั่งซึ่งเป็นคุณลักษณะที่ไม่สามารถหลีกเลี่ยงได้ของการทดแทนคำสั่ง (คุณสามารถหลีกเลี่ยงได้ แต่จากนั้นคำตอบก็ยิ่งซับซ้อนมากขึ้น)
Gilles 'ดังนั้นจงหยุดความชั่วร้าย'

6

ไม่แน่ใจว่าจะทำอย่างไรกับ shell builtins ( Gnouc ) แต่เครื่องมือมาตรฐานสามารถช่วย:

  1. คุณสามารถใช้wc -mอักขระที่นับได้ น่าเสียดายที่มันนับบรรทัดใหม่สุดท้ายด้วยดังนั้นคุณจะต้องกำจัดสิ่งนั้นเสียก่อน:

    readlink -f /etc/fstab | tr -d '\n' | wc -m
  2. แน่นอนคุณสามารถใช้ awk

    readlink -f /etc/fstab | awk '{print length($0)}'
  3. หรือ Perl

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

คุณหมายถึงexprเป็นแบบในตัวหรือไม่? เปลือกไหน
mikeserv

5

ฉันมักจะทำสิ่งนี้:

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

ในการทำคำสั่งฉันจะปรับมันตามต้องการ:

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

วิธีการนี้คล้ายกับสิ่งที่คุณทำใน 2 ขั้นตอนยกเว้นว่าเรารวมไว้ในหนึ่งซับ


2
คุณต้องใช้แทน-m -cด้วยอักขระ Unicode วิธีการของคุณจะถูกทำลาย
cuonglm

1
ทำไมไม่ง่าย ๆreadlink -f /etc/fstab | wc -m?
Phil

1
ทำไมคุณใช้วิธีการที่ไม่น่าเชื่อถือนี้แทน${#variable}? อย่างน้อยที่สุดก็ใช้คำพูดสองecho -n "$variable"แต่ยังคงล้มเหลวถ้าเช่นค่าของการมีvariable -eเมื่อคุณใช้ร่วมกับการทดแทนคำสั่งโปรดทราบว่าการขึ้นบรรทัดใหม่ต่อท้ายจะถูกตัดออก
Gilles 'หยุดความชั่วร้าย'

@philfrost b / c สิ่งที่ฉันแสดงให้เห็นสร้างขึ้นจากสิ่งที่ op คิดอยู่แล้ว นอกจากนี้ยังใช้งานได้กับ cmds ใด ๆ ที่เขาอาจมีการตั้งค่าก่อนใน vars และต้องการความยาวของพวกเขา afterwords terdon ก็มีตัวอย่างนั้นอยู่แล้ว
slm

1

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

zsh

ใน zsh คุณสามารถเขียน${#$(readlink -f /etc/fstab)}เพื่อรับความยาวของการทดแทนคำสั่ง โปรดทราบว่านี่ไม่ใช่ความยาวของเอาต์พุตคำสั่ง แต่เป็นความยาวของเอาต์พุตโดยไม่ขึ้นบรรทัดใหม่

หากคุณต้องการความยาวที่แน่นอนของเอาท์พุทเอาท์พุทอักขระที่ไม่ใช่บรรทัดใหม่พิเศษในตอนท้ายและลบออกหนึ่ง

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

หากสิ่งที่คุณต้องการคือเพย์โหลดในเอาต์พุตของคำสั่งคุณต้องลบสองอันที่นี่เพราะเอาต์พุตของreadlink -fคือพา ธ แบบบัญญัติและบวกบรรทัดใหม่

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

สิ่งนี้แตกต่างจาก${#$(readlink -f /etc/fstab)}ในกรณีที่หายาก แต่เป็นไปได้ซึ่งเส้นทางแบบบัญญัตินั้นสิ้นสุดในการขึ้นบรรทัดใหม่

สำหรับตัวอย่างที่เฉพาะเจาะจงนี้คุณไม่จำเป็นต้องเป็นสาธารณูปโภคภายนอกเลยเพราะ zsh มีในตัวสร้างที่เทียบเท่ากับการผ่านการปรับปรุงประวัติศาสตร์readlink -fA

echo /etc/fstab(:A)

ในการรับความยาวให้ใช้ตัวแก้ไขประวัติในการขยายพารามิเตอร์:

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

หากคุณมีชื่อไฟล์ในตัวแปรfilenameนั่นก็${#filename:A}คือ

กระสุนสไตล์ Bourne / POSIX

ไม่มีเชลล์ Bourne / POSIX บริสุทธิ์ (Bourne, Ash, mksh, ksh93, bash, yash …) มีส่วนขยายที่คล้ายกันที่ฉันรู้ หากคุณต้องการใช้การทดแทนพารามิเตอร์กับเอาต์พุตของการทดแทนคำสั่งหรือเพื่อทดแทนพารามิเตอร์ซ้อนให้ใช้ขั้นตอนต่อเนื่อง

คุณสามารถประมวลผลข้อมูลลงในฟังก์ชันได้หากต้องการ

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

หรือ

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

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

อีกครั้งผลลัพธ์ของreadlink -fคือเส้นทางที่เป็นที่ยอมรับบวกกับขึ้นบรรทัดใหม่ ถ้าคุณต้องการความยาวของเส้นทางที่ยอมรับลบ 2 แทน 1 command_output_lengthใน การใช้command_output_length_sans_trailing_newlinesจะให้ผลลัพธ์ที่ถูกต้องเฉพาะเมื่อเส้นทางแบบบัญญัติไม่ได้ขึ้นบรรทัดใหม่

ไบต์เทียบกับอักขระ

${#…}ควรจะเป็นความยาวเป็นตัวอักษรไม่ใช่ในไบต์ซึ่งสร้างความแตกต่างในสถานที่หลายไบต์ เวอร์ชั่นล่าสุดของ ksh93 ที่สมเหตุสมผล, bash และ zsh คำนวณความยาวเป็นอักขระตามค่าของLC_CTYPEเวลาที่การ${#…}สร้างถูกขยาย เชลล์ทั่วไปส่วนใหญ่อื่น ๆ ไม่สนับสนุนโลแคลหลายไบต์จริง ๆ : ในขณะที่ประ 0.5.7, mksh 46 และ posh 0.12.3 ${#…}ส่งคืนความยาวเป็นไบต์ หากคุณต้องการความยาวเป็นอักขระในวิธีที่เชื่อถือได้ให้ใช้wcยูทิลิตี้:

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

ตราบใดที่$LC_CTYPEกำหนดโลแคลที่ถูกต้องคุณสามารถมั่นใจได้ว่าสิ่งนี้จะเกิดข้อผิดพลาด (บนแพลตฟอร์มแบบโบราณหรือแบบ จำกัด ที่ไม่รองรับโลแคลหลายไบต์) หรือคืนความยาวที่ถูกต้องเป็นตัวอักษร (สำหรับ Unicode“ ความยาวเป็นตัวอักษร” หมายถึงจำนวนจุดโค้ด - จำนวนร่ายมนตร์เป็นอีกเรื่องหนึ่งเนื่องจากมีความซับซ้อนเช่นการรวมอักขระเข้าด้วยกัน)

หากคุณต้องการความยาวเป็นไบต์ตั้งLC_CTYPE=Cชั่วคราวหรือการใช้งานแทนwc -cwc -m

การนับไบต์หรืออักขระด้วยwcการขึ้นบรรทัดใหม่ต่อท้ายจากคำสั่ง หากคุณต้องการความยาวของเส้นทางแบบบัญญัติเป็นไบต์ก็เท่ากับ

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

ในการรับเป็นตัวอักษรให้ลบ 2


@cuonglm ไม่คุณต้องลบ 1 echo .เพิ่มอักขระสองตัว แต่อักขระตัวที่สองคือบรรทัดใหม่ที่ต่อท้ายซึ่งถูกปล้นโดยการทดแทนคำสั่ง
Gilles 'หยุดความชั่วร้าย'

ขึ้นบรรทัดใหม่จากreadlinkการส่งออกรวมทั้งโดย. echoเราทั้งสองตกลงว่าจะecho .เพิ่มตัวละครสองตัว แต่การขึ้นบรรทัดใหม่ถูกลาก ลองด้วยprintf .หรือดูคำตอบของฉันunix.stackexchange.com/a/160499/38906
cuonglm

@cuonglm คำถามที่ถามจำนวนตัวอักษรในผลลัพธ์ของคำสั่ง ผลลัพธ์ของreadlinkคือเป้าหมายลิงก์บวกกับขึ้นบรรทัดใหม่
Gilles 'หยุดความชั่วร้าย'

0

วิธีนี้ใช้งานได้dashแต่ไม่ต้องการให้ var เป้าหมายว่างเปล่าหรือไม่มีการตั้งค่าแน่นอน นี่คือเหตุผลว่าทำไมนี่จึงเป็นสองคำสั่ง - ฉันว่างเปล่า$lในตอนแรก:

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

เอาท์พุท

len is 10 and result is /etc/fstab

นั่นคือเชลล์บิวด์อินทั้งหมด - ไม่รวมreadlinkแน่นอน - แต่ประเมินในเชลล์ปัจจุบันด้วยวิธีนี้บอกเป็นนัยว่าคุณต้องทำการกำหนดค่าก่อนที่จะรับ len ซึ่งเป็นสาเหตุที่ฉัน%.sเลือกอาร์กิวเมนต์แรกในprintfสตริงรูปแบบและเพิ่มอีกครั้งสำหรับ ค่าตัวอักษรที่ส่วนท้ายของprintfรายการหาเรื่อง

ด้วยeval:

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

เอาท์พุท

10:/etc/fstab

คุณสามารถเข้าใกล้สิ่งเดียวกันได้ แต่แทนที่จะเอาท์พุทในตัวแปรในคำสั่งแรกที่คุณได้รับใน stdout:

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

... ซึ่งเขียน ...

10:/etc/fstab

... ถึง file descriptor 1 โดยไม่ต้องกำหนดค่าใด ๆ ให้กับ vars ใด ๆ ในเชลล์ปัจจุบัน


1
นั่นไม่ใช่สิ่งที่ OP ต้องการจะหลีกเลี่ยงใช่ไหม "ฉันเข้าใจว่าเป็นไปได้ที่จะทำสิ่งนี้โดยการบันทึกผลลัพธ์เป็นตัวแปรแรก: variable=$(readlink -f /etc/fstab); echo ${#variable};แต่ฉันต้องการลบขั้นตอนเพิ่มเติม"
terdon

@terdon ฉันอาจเข้าใจผิด แต่มันเป็นความประทับใจของฉันที่เครื่องหมายอัฒภาคเป็นปัญหาและไม่ใช่ตัวแปร นั่นเป็นเหตุผลที่สิ่งเหล่านี้ได้รับ len และเอาต์พุตในคำสั่งแบบง่าย ๆ โดยใช้ shell builtins เท่านั้น เชลล์ไม่เรียกใช้ readlink สำหรับexec จากนั้นเรียกใช้ exec exprตัวอย่างเช่น มันอาจจะเพียงเรื่องถ้าอย่างใดได้รับ occludes len ค่าซึ่งผมยอมรับว่าผมมีความเข้าใจความยากลำบากเหตุผลที่อาจจะมี แต่ผมสงสัยว่าอาจจะมีกรณีที่มันสำคัญ
mikeserv

1
evalวิธีโดยวิธีการที่น่าจะเป็นที่สะอาดนี่ - มันกำหนดออกและ len กับชื่อ var เดียวกันในการดำเนินการเดียว - มากl=length(l):out(l)ใกล้เคียงกับการทำ การทำexpr length $(command) ไม่ได้ปิดกั้นค่านิยมของ len โดยวิธีการ
mikeserv
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.