2011-01-18 27 views
5

(CHỈNH SỬA trong giải pháp có sed)Bash thoát dấu ngã và ký tự đại diện nhưng không phải dấu cách

Tôi có danh sách tên và thư mục, bao gồm dấu ngã và ký tự đại diện. Ví dụ .:

~/docs/file.*  
~/my docs/*.txt 
... 

Tôi đọc các hàng và vượt qua chúng để một lệnh (ví dụ rsync):

while read ROW 
do 
    rsync $ROW /my/dest/ 
done < list.txt 

Vấn đề là xử lý với khoảng trống trong tên tập tin. Nếu tôi đặt $ ROW trong dấu ngoặc kép như thế này

rsync "$ROW" /my/dest/ 

bash khóa không thoát khỏi ký tự đại diện và dấu ngã. Nhưng nếu tôi không sử dụng dấu ngoặc kép thì không gian sẽ phá vỡ hàng.

Một giải pháp có thể là thay đổi IFS (cẩn thận: tập lệnh phức tạp hơn tập lệnh mà tôi đã báo cáo). Một giải pháp khác (nhờ sửa chữa cho Patrick Echterbruch) là để thoát khỏi không gian. Tuy nhiên, mã sau không hoạt động đối với tôi:

while read ROW 
do 
    export NEWROW=$(echo $ROW | sed -e 's/ /\\ /g') 
    echo "Value: $NEWROW" 
    ls -1d $NEWROW 
done < list.txt 

Xin lưu ý rằng không có dấu ngoặc kép nào được chuyển cho ls. Các tập tin "~/a b c/test.txt" tồn tại, nhưng tôi nhận được:

Value: ~/saver/a\ b\ c/*.txt 
ls: impossibile accedere a ~/saver/a\: Nessun file o directory 
ls: impossibile accedere a b\: Nessun file o directory 
ls: impossibile accedere a c/*.txt: Nessun file o directory 

Có vẻ như NEWROW được chuyển cho ls như là một chuỗi, do đó ký tự đại diện không được mở rộng nhưng không gian giữ vi phạm tên tập tin, mặc dù đã trốn thoát.

Mẹo nào? Cảm ơn.

+0

Vui lòng xem [điều này liên quan đến việc mở rộng dấu ngã] (http://mywiki.wooledge.org/BashPitfalls#echo_.22.2BAH4.22). –

Trả lời

1

Theo như tôi thấy, tập lệnh sed bạn đã đăng sẽ không có hiệu lực - các phần "tìm kiếm" và "thay thế" giống nhau.

gì bạn có thể làm có thể làm:

ROW=$(echo $ROW | sed -e "s/ /\\\\ /g") 

hoặc (tốt hơn):

ROW=$(echo $ROW | sed -e 's/ /\\ /g') 

Sau dụ sử dụng dấu nháy đơn, do đó vỏ không điều trị các dấu chéo ngược đặc biệt.

Liên quan đến vấn đề thứ hai phát sinh: Các ký tự đặc biệt (như ~) được mở rộng bash khi chúng gặp phải trong các tham số cho chương trình. Chúng sẽ không được mở rộng khi lưu trữ chúng trong một biến, cũng như sẽ không bash kiểm tra và mở rộng nội dung của một biến khi truyền nó như tham số cho một lệnh.

Đây là lý do tại sao rsync $NEWROW ... không hoạt động - rsync nhận nội dung chưa sửa đổi của $ NEWROW làm thông số đầu tiên và sau đó sẽ phải thực hiện mở rộng giống như vỏ trên chính nó.

Cách giải quyết duy nhất mà tôi được biết đến là chạy lệnh trong một subshell, ví dụ:

ROW="~/a b c" 
$NEWROW=$(echo $ROW | sed -e 's/ /\\ /g') 
bash -c "ls -ld $f" 

này sẽ làm cho bash hiện đang chạy để trích xuất các biến nội dung (tức là ~/a\ b\ c và vượt qua nó để vỏ thứ hai nó sẽ được gọi ra, bash "inner" này sẽ nhận lệnh cùng với các tham số và sau đó tất nhiên sẽ mở rộng tham số.

Rất tiếc vì đã bỏ lỡ điểm này ngay từ đầu, tôi chỉ tập trung vào phần regex của câu hỏi.

Tìm thấy một giải pháp khả thi: Sau khi chuyển đổi con đường sử dụng sed (ví dụ NEWROW=$(echo $ROW | sed -e 's/ /\\ /g') thử:

eval ls -ld $NEWROW 

hoặc eval rsync $ NEWROW/khác/path

này nên làm việc là tốt, mà không thực hiện tác động của bắt đầu subshells

// EDIT: Đã thêm thiếu g vào tập lệnh sed

// EDIT: Thêm cách giải quyết cho vấn đề mở rộng tên đường dẫn

// EDIT: Addes giải pháp eval

+0

Bạn có thể chạy trên máy cục bộ của mình không? Tôi không thể làm việc này được. – chrisaycock

+0

Có, nó chạy hoàn toàn tốt. Lệnh: ROW = "tài liệu của tôi"; echo $ ROW; ROW = $ (echo $ ROW | sed -e 's// \\ /'); echo $ ROW trả về hai dòng, một dòng có dấu cách thuần túy, một dòng còn lại có dấu cách thoát ngược. sed --version: GNU sed phiên bản 4.2 –

+1

Sau đó, chúng tôi đang thiếu "g" cuối cùng để thay thế mọi lần xuất hiện của không gian và không chỉ lần xuất hiện đầu tiên: 's// \\/g'. Bây giờ tôi tự hỏi làm thế nào để bỏ qua không gian đã trốn thoát, nhưng tôi nghĩ rằng có những câu hỏi khác về nó. – Narcolessico

0

Tôi nghĩ rằng bạn có thể đường ống trong danh sách là rsync vào một tập tin riêng biệt, và thực hiện nó, giống như

while read ROW 
do 
    echo "rsync '$ROW' /my/dest/" 
done <list.txt> rsync.txt 

sh rsync.txt 
+0

Giải pháp thú vị, nhưng có thể hơi chậm (đặc biệt là nếu các ký tự đại diện mở rộng mang lại nhiều tệp). – Narcolessico

Các vấn đề liên quan