tra-loi-cau-hoi-phat-trien-web.com

Trích xuất chuỗi con trong Bash

Đặt tên tệp ở dạng someletters_12345_moreleters.ext, tôi muốn trích xuất 5 chữ số và đặt chúng vào một biến.

Vì vậy, để nhấn mạnh điểm, tôi có một tên tệp với x số ký tự sau đó một chuỗi năm chữ số được bao quanh bởi một dấu gạch dưới ở hai bên sau đó là một bộ x số ký tự khác. Tôi muốn lấy số có 5 chữ số và đặt nó vào một biến.

Tôi rất quan tâm đến số lượng các cách khác nhau mà điều này có thể được thực hiện.

600
Berek Bryan

Sử dụng cắt :

echo 'someletters_12345_moreleters.ext' | cut -d'_' -f 2

Chung hơn:

INPUT='someletters_12345_moreleters.ext'
SUBSTRING=$(echo $INPUT| cut -d'_' -f 2)
echo $SUBSTRING
591
FerranB

Nếu x là hằng số, việc mở rộng tham số sau đây thực hiện trích xuất chuỗi con:

b=${a:12:5}

trong đó 12 là phần bù (dựa trên zero) và 5 là độ dài

Nếu dấu gạch dưới xung quanh các chữ số là số duy nhất trong đầu vào, bạn có thể loại bỏ tiền tố và hậu tố (tương ứng) theo hai bước:

tmp=${a#*_}   # remove prefix ending in "_"
b=${tmp%_*}   # remove suffix starting with "_"

Nếu có các dấu gạch dưới khác, dù sao thì nó cũng có thể khả thi, mặc dù khó khăn hơn. Nếu bất cứ ai biết cách thực hiện cả hai lần mở rộng trong một biểu thức, tôi cũng muốn biết.

Cả hai giải pháp được trình bày là bash thuần túy, không có quá trình sinh sản liên quan, do đó rất nhanh.

930
JB.

Giải pháp chung trong đó số có thể ở bất kỳ đâu trong tên tệp, sử dụng chuỗi đầu tiên như vậy:

number=$(echo $filename | egrep -o '[[:digit:]]{5}' | head -n1)

Một giải pháp khác để trích xuất chính xác một phần của biến:

number=${filename:offset:length}

Nếu tên tệp của bạn luôn có định dạng stuff_digits_..., bạn có thể sử dụng awk:

number=$(echo $filename | awk -F _ '{ print $2 }')

Một giải pháp khác để loại bỏ mọi thứ trừ chữ số, sử dụng

number=$(echo $filename | tr -cd '[[:digit:]]')
85

chỉ cần cố gắng sử dụng cut -c startIndx-stopIndx

75
brown.2179

Trong trường hợp ai đó muốn thông tin nghiêm ngặt hơn, bạn cũng có thể tìm kiếm nó trong man bash như thế này

$ man bash [press return key]
/substring  [press return key]
[press "n" key]
[press "n" key]
[press "n" key]
[press "n" key]

Kết quả:

[.__.] $ {tham số: offset} [.__.] $ {tham số: offset: length} [.__.] Mở rộng chuỗi con. Mở rộng tối đa các ký tự của tham số [.__.] Bắt đầu từ ký tự được chỉ định bởi offset. Nếu [.__.] Độ dài bị bỏ qua, mở rộng sang chuỗi con của tham số bắt đầu - [.__.] Ing tại ký tự được chỉ định bởi offset. độ dài và độ lệch là [.___.] biểu thức số học (xem ĐÁNH GIÁ ARITHMETIC bên dưới). Nếu phần bù [.__.] Ước tính thành một số nhỏ hơn 0, giá trị được sử dụng [.__.] Làm phần bù từ phần cuối của giá trị của tham số. Các biểu thức số học [.__.] Bắt đầu bằng a - phải được phân tách bằng khoảng trắng [.__.] Từ trước: để được phân biệt với Sử dụng mặc định [.__.] Nếu độ dài ước tính thành một số nhỏ hơn [.__.] 0 và tham số không phải là @ và không phải là mảng được lập chỉ mục hoặc liên kết [.__. của tham số chứ không phải là một số ký tự và expan - [.__.] sion là các ký tự giữa hai độ lệch. Nếu tham số là [.__.] @, Kết quả là độ dài tham số vị trí bắt đầu lúc tắt - [.__.] Được đặt. Nếu tham số là tên mảng được lập chỉ mục được đăng ký bởi @ hoặc [.__.] *, Kết quả là các thành viên có độ dài của mảng bắt đầu bằng [.__ Một phần bù âm được lấy tương đối so với [.__.] Lớn hơn chỉ số tối đa của mảng được chỉ định. Sub - [.__.] Mở rộng chuỗi được áp dụng cho một mảng kết hợp tạo ra unde - [.__.] Kết quả bị phạt. Lưu ý rằng phần bù âm phải được tách [.__.] Với dấu hai chấm bằng ít nhất một khoảng trắng để tránh bị nhầm lẫn [.__.] Với phần mở rộng: -. Lập chỉ mục chuỗi con là không dựa trên trừ khi [.__.] Các tham số vị trí được sử dụng, trong trường hợp đó, việc lập chỉ mục [.__ Nếu độ lệch là 0 và các tham số [.__.] Vị trí được sử dụng, $ 0 được thêm tiền tố vào danh sách. [.__.]
31
jperelli

Tôi ngạc nhiên khi giải pháp bash tinh khiết này không xuất hiện:

a="someletters_12345_moreleters.ext"
IFS="_"
set $a
echo $2
# prints 12345

Bạn có thể muốn đặt lại IFS về giá trị trước đó hoặc unset IFS sau đó!

18
user1338062

Dựa trên câu trả lời của jor (không phù hợp với tôi):

substring=$(expr "$filename" : '.*_\([^_]*\)_.*')
18
PEZ

Theo yêu cầu

Tôi có một tên tệp với x số ký tự, sau đó là một chuỗi năm chữ số được bao quanh bởi một dấu gạch dưới ở hai bên sau đó là một bộ x số ký tự khác. Tôi muốn lấy số có 5 chữ số và đặt nó vào một biến.

Tôi đã tìm thấy một số cách grep có thể hữu ích:

$ echo "someletters_12345_moreleters.ext" | grep -Eo "[[:digit:]]+" 
12345

hoặc tốt hơn

$ echo "someletters_12345_moreleters.ext" | grep -Eo "[[:digit:]]{5}" 
12345

Và sau đó với cú pháp -Po:

$ echo "someletters_12345_moreleters.ext" | grep -Po '(?<=_)\d+' 
12345

Hoặc nếu bạn muốn làm cho nó phù hợp với chính xác 5 ký tự:

$ echo "someletters_12345_moreleters.ext" | grep -Po '(?<=_)\d{5}' 
12345

Cuối cùng, để làm cho nó được lưu trữ trong một biến, chỉ cần sử dụng cú pháp var=$(command).

11
fedorqui

Nếu không có bất kỳ quy trình phụ nào bạn có thể:

shopt -s extglob
front=${input%%_+([a-zA-Z]).*}
digits=${front##+([a-zA-Z])_}

Một biến thể rất nhỏ của điều này cũng sẽ hoạt động trong ksh93.

10
Darron

Nếu chúng ta tập trung vào khái niệm:
[.__.] "Một loạt (một hoặc vài) chữ số"

Chúng ta có thể sử dụng một số công cụ bên ngoài để trích xuất các con số.
[.__.] Chúng tôi có thể dễ dàng xóa tất cả các ký tự khác, kể cả sed hoặc tr:

name='someletters_12345_moreleters.ext'

echo $name | sed 's/[^0-9]*//g'    # 12345
echo $name | tr -c -d 0-9          # 12345

Nhưng nếu $ name chứa một vài lần chạy số, điều trên sẽ thất bại:

Nếu "name = someletters_12345_moreleter_323_end.ext", thì:

echo $name | sed 's/[^0-9]*//g'    # 12345323
echo $name | tr -c -d 0-9          # 12345323

Chúng ta cần sử dụng expresions thường xuyên (regex).
[.___.] Để chỉ chọn lần chạy đầu tiên (12345 chứ không phải 323) trong sed và Perl:

echo $name | sed 's/[^0-9]*\([0-9]\{1,\}\).*$/\1/'
Perl -e 'my $name='$name';my ($num)=$name=~/(\d+)/;print "$num\n";'

Nhưng chúng ta cũng có thể làm điều đó trực tiếp trong bash (1) :

regex=[^0-9]*([0-9]{1,}).*$; \
[[ $name =~ $regex ]] && echo ${BASH_REMATCH[1]}

Điều này cho phép chúng tôi trích xuất chuỗi chữ số đầu tiên có độ dài bất kỳ
[.___.] Được bao quanh bởi bất kỳ văn bản/ký tự nào khác.

Lưu ý : regex=[^0-9]*([0-9]{5,5}).*$; sẽ chỉ khớp chính xác với 5 lần chạy. :-)

(1): nhanh hơn gọi một công cụ bên ngoài cho mỗi văn bản ngắn. Không nhanh hơn thực hiện tất cả xử lý bên trong sed hoặc awk cho các tệp lớn.

10
user2350426

Đây là một giải pháp tiền tố hậu tố (tương tự như các giải pháp được đưa ra bởi JB và Darron) khớp với khối chữ số đầu tiên và không phụ thuộc vào các dấu gạch dưới xung quanh:

str='someletters_12345_morele34ters.ext'
s1="${str#"${str%%[[:digit:]]*}"}"   # strip off non-digit prefix from str
s2="${s1%%[^[:digit:]]*}"            # strip off non-digit suffix from s1
echo "$s2"                           # 12345
9
codist

Đây là cách tôi sẽ làm điều đó:

FN=someletters_12345_moreleters.ext
[[ $FN =~ _([[:digit:]]{5})_ ]] && NUM=${BASH_REMATCH[1]}

Lưu ý: ở trên là biểu thức chính quy và được giới hạn trong kịch bản cụ thể của bạn gồm năm chữ số được bao quanh bởi dấu gạch dưới. Thay đổi biểu thức chính quy nếu bạn cần kết hợp khác nhau.

8
nicerobot

Tôi yêu khả năng của sed để đối phó với các nhóm regex:

> var="someletters_12345_moreletters.ext"
> digits=$( echo $var | sed "s/.*_\([0-9]\+\).*/\1/p" -n )
> echo $digits
12345

Tùy chọn tổng quát hơn một chút sẽ là không giả sử rằng bạn có dấu gạch dưới _ đánh dấu bắt đầu chuỗi chữ số của bạn, do đó, ví dụ như tước bỏ tất cả các số không bạn nhận được trước chuỗi: s/[^0-9]\+\([0-9]\+\).*/\1/p.


> man sed | grep s/regexp/replacement -A 2
s/regexp/replacement/
    Attempt to match regexp against the pattern space.  If successful, replace that portion matched with replacement.  The replacement may contain the special  character  &  to
    refer to that portion of the pattern space which matched, and the special escapes \1 through \9 to refer to the corresponding matching sub-expressions in the regexp.

Thêm về điều này, trong trường hợp bạn không quá tự tin với regexps:

  • s dành cho _s_ubstolarship
  • [0-9]+ khớp với hơn 1 chữ số
  • \1 liên kết đến nhóm n.1 của đầu ra regex (nhóm 0 là toàn bộ khớp, nhóm 1 là khớp trong ngoặc đơn trong trường hợp này)
  • Cờ p dành cho _p_rinting

Tất cả các lối thoát \ đều ở đó để làm cho quá trình xử lý regrec của sed.

6
Campa

Cho test.txt là một tệp chứa "ABCDEFGHIJKLMNOPQRSTUVWXYZ"

cut -b19-20 test.txt > test1.txt # This will extract chars 19 & 20 "ST" 
while read -r; do;
> x=$REPLY
> done < test1.txt
echo $x
ST
5
Rick Osman

Câu trả lời của tôi sẽ có nhiều quyền kiểm soát hơn đối với những gì bạn muốn từ chuỗi của bạn. Đây là mã về cách bạn có thể trích xuất 12345 ra khỏi chuỗi của mình

str="someletters_12345_moreleters.ext"
str=${str#*_}
str=${str%_more*}
echo $str

Điều này sẽ hiệu quả hơn nếu bạn muốn trích xuất thứ gì đó có bất kỳ ký tự nào như abc hoặc bất kỳ ký tự đặc biệt nào như _ hoặc -. Ví dụ: Nếu chuỗi của bạn giống như thế này và bạn muốn mọi thứ nằm sau someletters_ và trước _moreleters.ext:

str="someletters_123-45-24a&13b-1_moreleters.ext"

Với mã của tôi, bạn có thể đề cập chính xác những gì bạn muốn. Giải trình:

#* Nó sẽ xóa chuỗi trước bao gồm cả khóa khớp. Ở đây khóa chúng tôi đã đề cập là _% Nó sẽ xóa chuỗi sau bao gồm cả khóa khớp. Ở đây, chìa khóa chúng tôi đã đề cập là '_more *'

Tự làm một số thí nghiệm và bạn sẽ thấy điều này thú vị.

3
Alex Raj Kaliamoorthy

Ok, ở đây đi thay thế tham số thuần túy với một chuỗi rỗng. Hãy cẩn thận là tôi đã xác định someletters moreletters chỉ là các ký tự. Nếu chúng là chữ và số, điều này sẽ không hoạt động như vậy.

filename=someletters_12345_moreletters.ext
substring=${filename//@(+([a-z])_|_+([a-z]).*)}
echo $substring
12345
2
morbeo

tương tự như chất nền ('abcdefg', 2-1, 3) trong php:

echo 'abcdefg'|tail -c +2|head -c 3
2
diyism

Ngoài ra còn có lệnh bash dựng sẵn 'expr':

INPUT="someletters_12345_moreleters.ext"  
SUBSTRING=`expr match "$INPUT" '.*_\([[:digit:]]*\)_.*' `  
echo $SUBSTRING
1
jor

Hơi muộn một chút, nhưng tôi chỉ chạy qua vấn đề này và thấy như sau:

Host:/tmp$ asd=someletters_12345_moreleters.ext 
Host:/tmp$ echo `expr $asd : '.*_\(.*\)_'`
12345
Host:/tmp$ 

Tôi đã sử dụng nó để có được độ phân giải mili giây trên một hệ thống nhúng không có% N cho ngày:

set `grep "now at" /proc/timer_list`
nano=$3
fraction=`expr $nano : '.*\(...\)......'`
$debug nano is $nano, fraction is $fraction
1
russell

Một giải pháp bash:

IFS="_" read -r x digs x <<<'someletters_12345_moreleters.ext'

Điều này sẽ ghi đè một biến gọi là x. Var x có thể được thay đổi thành var _.

input='someletters_12345_moreleters.ext'
IFS="_" read -r _ digs _ <<<"$input"
1
user2350426