Làm thế nào để tách tên tập tin thành biến?


11

Giả sử tôi có một danh sách các tệp csv với định dạng sau:

INT_V1_<Product>_<ID>_<Name>_<ddmmyy>.csv
ASG_B1_V1_<Product>_<ID>_<Name>_<ddmmyy>.csv

Các INT_V1_ & ASG_B1_V1_ là cố định, có nghĩa là tất cả các file csv bắt đầu với nó.
Làm thế nào tôi có thể chia tên tập tin thành biến?
Ví dụ, tôi muốn chụp Tên và gán nó cho một biến $Name.


Tại sao thẻ "bash", nếu bạn đang sử dụng ksh trên AIX 7.1?
Stéphane Chazelas

Tôi muốn sản xuất một kịch bản bash. Chỉ là tôi muốn thử nó đầu tiên trên ksh, xin lỗi vì đã gây rắc rối cho bạn.
Juliet.Y

Câu trả lời:


7

Với zsh:

file='INT_V1_<Product>_<ID>_<Name>_<ddmmyy>.csv'

setopt extendedglob
if [[ $file = (#b)*_(*)_(*)_(*)_(*).csv ]]; then
  product=$match[1] id=$match[2] name=$match[3] date=$match[4]
fi

Với bash4.3 hoặc mới hơn, ksh93t hoặc mới hơn hoặc zsh trong mô phỏng sh (mặc dù trong zsh, bạn chỉ đơn giản là làm field=("${(@s:_:)field}")để phân tách hơn là sử dụng toán tử vô nghĩa split + global sh), bạn có thể tách chuỗi trên các _ký tự và tham chiếu chúng từ cuối :

IFS=_
set -o noglob
field=($file) # split+glob  operator
date=${field[-1]%.*}
name=${field[-2]}
id=${field[-3]}
product=${field[-4]}

Hoặc (bash 3.2 trở lên):

if [[ $file =~ .*_(.*)_(.*)_(.*)_(.*)\.csv$ ]]; then
  product=${BASH_REMATCH[1]}
  id=${BASH_REMATCH[2]}
  name=${BASH_REMATCH[3]}
  date=${BASH_REMATCH[4]}
fi

(giả định đó $filechứa văn bản hợp lệ trong ngôn ngữ hiện tại không được bảo đảm cho tên tệp trừ khi bạn sửa miền địa phương thành C hoặc miền địa phương khác với bộ ký tự một byte cho mỗi ký tự).

Giống như zsh*ở trên, .*tham lam . Vì vậy, người đầu tiên sẽ ăn càng nhiều *_càng tốt, do đó, người còn lại .*sẽ chỉ kết hợp các _chuỗi không có kết quả .

Với ksh93, bạn có thể làm

pattern='*_(*)_(*)_(*)_(*).csv'
product=${file//$pattern/\1}
id=${file//$pattern/\2}
name=${file//$pattern/\3}
date=${file//$pattern/\4}

Trong một POSIX shkịch bản, bạn có thể sử dụng ${var#pattern}, ${var%pattern}khai thác mở rộng tham số tiêu chuẩn:

rest=${file%.*} # remove .csv suffix
date=${rest##*_} # remove everything on the left up to the rightmost _
rest=${rest%_*} # remove one _* from the right
name=${rest##*_}
rest=${rest%_*}
id=${rest##*_}
rest=${rest%_*}
product=${rest##*_}

Hoặc sử dụng lại toán tử split + global:

IFS=_
set -o noglob
set -- $file
shift "$(($# - 4))"
product=$1 id=$2 name=$3 date=${4%.*}

Tôi đang sử dụng bash trên AIX7.1 và tôi hiện đang thử nghiệm trong ksh. Bằng cách nào đó tôi gặp phải một lỗi nêu ksh: file: 0403-046 The specified subscript cannot be greater than 4095.cho ${field[-1]}hay bất cứ điều gì trong các hình thức ${x[n]}.
Juliet.Y

@Juliet, ${field[-1]}là cho bash-4.3+. Đối với ksh, sử dụng bất kỳ giải pháp "POSIX" nào. Hỗ trợ cho đăng ký phủ định không được thêm vào trước ksh93t (một tính năng bắt nguồn từ zsh).
Stéphane Chazelas

OK lưu ý. Cảm ơn rất nhiều, các kịch bản đang hoạt động tốt.
Juliet.Y

4

Bạn có thể lấy các giá trị của trường của bạn <Name>bằng lệnh này:

cut -d'<' -f4 < csvlist | sed -e 's/>_//g'

(hoặc với awk):

awk -F'<' '{print $4}' < csvlist | sed -e 's/>_//g'

Và bạn có thể đặt chúng vào một biến như thế này:

variable_name=$(cut -d'<' -f4 < csvlist | sed -e 's/>_//g')

hoặc là

awk -F'<' '{print $4}' < csvlist | sed -e 's/>_//g'

Không rõ ràng trong câu hỏi nếu bạn muốn cùng một biến cho tất cả các giá trị hoặc một biến duy nhất cho mỗi một trong số chúng.


1
file='INT_V1_<Product>_<ID>_<Name>_<ddmmyy>.csv'
IFS=\_ read -r x x product id name date x <<< "$file"
date=${date%.*}

Lưu ý rằng _nó không đặc biệt và không cần trích dẫn. Giả sử tên tệp không chứa ký tự dòng mới. Bạn có thể muốn thêm một -d ''.
Stéphane Chazelas
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.