Làm thế nào để làm điều này một cách chính xác
Trước hết, luôn luôn trích dẫn các biến của bạn . Những gì bạn đang cố gắng làm tốt nếu bạn trích dẫn đúng:
$ pwd
/home/terdon/foo/\e[92mM@r|< +'|'|_e|\|\|0rth [`-_-"]
$ ls
pullingATerdon
Tôi đã giữ tên tệp lạ mà bạn đã chọn ( mặc dù tôi không biết tại sao bạn chọn nó ) vì mục đích nhất quán.
Bây giờ, hãy gán đường dẫn của pullingATerdon
một biến và sau đó thử mở tệp:
$ bacon="$(realpath pullingATerdon)"
$ echo "$bacon"
/home/terdon/foo/\e[92mM@r|< +'|'|_e|\|\|0rth [`-_-"]/pullingATerdon
$ ls $bacon
ls: cannot access '+'\''|'\''|_e|\|\|0rth': No such file or directory
ls: cannot access '[`-_-"]/pullingATerdon': No such file or directory
'/home/terdon/foo/\e[92mM@r|<':
Điều đó thất bại, như mong đợi. Nhưng, nếu bây giờ chúng tôi trích dẫn chính xác:
$ ls -l "$bacon"
-rw-r--r-- 1 terdon terdon 0 Mar 14 23:15 '/home/terdon/foo/\e[92mM@r|< +'\''|'\''|_e|\|\|0rth [`-_-"]/pullingATerdon'
Nó hoạt động như mong đợi. Và vâng, bạn cũng có thể mở đường dẫn trong trình chỉnh sửa (thích hợp): emacs "$bacon"
sẽ hoạt động tốt. OK, sẽ vim
và bất cứ điều gì khác. Sự lựa chọn của bạn về biên tập viên, mặc dù không may, không liên quan.
Tại sao bạn thất bại
Một cách nhanh chóng để theo dõi những gì thực sự xảy ra trong trường hợp của bạn là sử dụng set -x
(tắt nó một lần nữa set +x
), điều này khiến shell in mỗi lệnh mà nó sẽ chạy trước khi chạy nó. bật thông báo gỡ lỗi của shell với set -x
:
$ set -x
$ /bin/ls $bacon
+ ls '/home/terdon/foo/\e[92mM@r|<' '+'\''|'\''|_e|\|\|0rth' '[`-_-"]/pullingATerdon'
ls: cannot access '+'\''|'\''|_e|\|\|0rth': No such file or directory
ls: cannot access '[`-_-"]/pullingATerdon': No such file or directory
'/home/terdon/foo/\e[92mM@r|<':
Đó là chương trình chúng ta biết rằng ls
đã chạy với ba đối số riêng biệt: '/home/terdon/foo/\e[92mM@r|<'
, '+'\''|'\''|_e|\|\|0rth'
và '[`-_-"]/pullingATerdon'
. Điều này xảy ra bởi vì shell thực hiện phân tách từ và mở rộng toàn cầu trên các chuỗi không được trích dẫn. Trong trường hợp này, vấn đề là phân tách từ, vì shell nhìn thấy các khoảng trắng trong đường dẫn và đọc từng chuỗi được phân tách bằng dấu cách là một đối số riêng biệt.
Các mkdir
ví dụ là hơi khác nhau nhưng điều đó vì bạn đang hiển thị chúng tôi được thông báo lỗi từ thứ hai gọi của lệnh. Tôi đoán bạn đã thử nó một lần, và sau đó chạy nó lần thứ hai để có được đầu ra cho câu hỏi của bạn. Lần đầu tiên bạn chạy nó, nó sẽ trông như thế này:
$ mkdir $(realpath pullingATerdon)
++ realpath pullingATerdon
+ mkdir '/home/terdon/foo/\e[92mM@r|<' '+'\''|'\''|_e|\|\|0rth' '[`-_-"]/pullingATerdon'
mkdir: cannot create directory ‘[`-_-"]/pullingATerdon’: No such file or directory
Một lần nữa, điều đó sẽ cố gắng tạo ba thư mục, không phải một, vì chia tách từ. Đầu tiên, nó tạo (thành công) thư mục /home/terdon/foo/\e[92mM@r|<
:
$ ls -l /home/terdon/foo/
total 8
drwxr-xr-x 2 terdon terdon 4096 Mar 15 00:20 '\e[92mM@r|<'
drwxr-xr-x 3 terdon terdon 4096 Mar 15 00:20 '\e[92mM@r|< +'\''|'\''|_e|\|\|0rth [`-_-"]'
Sau đó, nó cũng thành công, tạo ra một thư mục được gọi +'|'|_e|\|\|0rth
trong thư mục hiện tại của bạn:
$ ls -l
total 4
drwxr-xr-x 2 terdon terdon 4096 Mar 15 00:37 '+'\''|'\''|_e|\|\|0rth'
-rw-r--r-- 1 terdon terdon 0 Mar 15 00:36 pullingATerdon
Và sau đó, nó đã cố gắng tạo thư mục [`-_-"]/pullingATerdon
. Điều này không thành công bởi vì mkdir
theo mặc định, không tạo thư mục con (có thể, nếu bạn chạy nó với -p
):
$ mkdir baz/bar
mkdir: cannot create directory ‘baz/bar’: No such file or directory
Vì chuỗi không được trích dẫn của bạn chứa a /
, mkdir
được coi là một đường dẫn gồm hai thư mục, đã cố gắng tìm thư mục trên cùng và không thành công.
Đó là lý do tại sao nó thất bại, nhưng những gì đã xảy ra thì phức tạp hơn. Chuỗi bạn sử dụng thực sự là một glob vỏ, đặc biệt là một loạt glob , mà phù hợp với tất cả các file trong thư mục hiện có tên là một trong những nhân vật 5 `
, -
, _
hoặc "
. Vì bạn không có các tệp như vậy trong thư mục hiện tại của mình, nên toàn cầu không khớp với bất cứ thứ gì và, như là hành vi mặc định trong bash, sẽ tự trả về:
$ echo "[\`-_-\"]/pullingATerdon" ## some escaping is needed here
+ echo '[`-_-"]/pullingATerdon' ## but it echoes the right thing
[`-_-"]/pullingATerdon ## and matches nothing, so returns itself.
Để làm rõ, đây là những gì sẽ xảy ra nếu bạn đưa ra một quả cầu phù hợp với một cái gì đó:
$ echo [p]* ## any filename starting with a p
pullingATerdon
$ echo "[p]*" ## the string "[p]*"
[p]*
Không trích dẫn [p*]
được mở rộng vào danh sách các tên tệp phù hợp (chỉ một, trong trường hợp này) và đó là những gì được chuyển đến echo
. Một lý do khác tại sao bạn nên trích dẫn tất cả mọi thứ.
Cuối cùng, lỗi thực tế bạn hiển thị là từ lần thứ hai bạn chạy lệnh và nó bị lỗi ở bước đầu tiên, khi cố gắng tạo /home/terdon/foo/\e[92mM@r|<
, vì lệnh gọi trước đó đã tạo thư mục đó.
Tổng quát hơn, bất cứ khi nào bạn thấy mình làm việc với các tên tệp tùy ý, luôn luôn sử dụng các khối vỏ. Những thứ như thế này:
for file in *; do command "$file"; done
Điều đó sẽ làm việc cho bất kỳ tên tập tin. Không có vấn đề gì xảy ra để chứa. Trong ví dụ của chúng tôi ở trên, bạn có thể đã thực hiện:
emacs /home/terdon/*92mM*/pullingATerdon
Bất kỳ quả cầu nào xác định tệp mục tiêu duy nhất sẽ làm. Bằng cách đó, bạn không cần phải lo lắng về các ký tự đặc biệt và chỉ có thể để vỏ xử lý chúng.
Một số tài liệu tham khảo hữu ích:
Làm cách nào tôi có thể tìm và xử lý một cách an toàn tên tệp chứa dòng mới, dấu cách hoặc cả hai? : Một trong những Câu hỏi thường gặp trên Wiki của Grey Cat xuất sắc.
Ý nghĩa bảo mật của việc quên trích dẫn một biến trong shell bash / POSIX : cùng một bài tôi đã tham chiếu ở đầu câu trả lời này. Một lời giải thích tuyệt vời và rất chi tiết về tất cả những điều có thể sai nếu bạn không trích dẫn chính xác các biến shell của mình.
Tại sao kịch bản shell của tôi bị sặc trên khoảng trắng hoặc các ký tự đặc biệt khác? : mọi thứ bạn từng muốn biết về việc xử lý tên tệp tùy ý trong trình bao.
Khi nào cần trích dẫn kép? : Tìm hiểu thêm về dấu ngoặc kép và biến và cụ thể, một vài trường hợp bạn không cần trích dẫn chúng
vim "$bacon"