Trích dẫn trong ssh $ host $ FOO và ssh $ host Tấn sudo su user -c $ FOO Kiểu cấu trúc


30

Tôi thường kết thúc việc ban hành các lệnh phức tạp trên ssh; các lệnh này liên quan đến đường ống đến awk hoặc perl một dòng và kết quả là chứa các dấu ngoặc đơn và $ 's. Tôi đã không thể tìm ra một quy tắc cứng và nhanh để thực hiện trích dẫn đúng, cũng không tìm thấy một tài liệu tham khảo tốt cho nó. Ví dụ, hãy xem xét những điều sau đây:

# what I'd run locally:
CMD='pgrep -fl java | grep -i datanode | awk '{print $1}'
# this works with ssh $host "$CMD":
CMD='pgrep -fl java | grep -i datanode | awk '"'"'{print $1}'"'"

(Lưu ý các trích dẫn thêm trong câu lệnh awk.)

Nhưng làm thế nào để tôi làm việc này, vd ssh $host "sudo su user -c '$CMD'"? Có một công thức chung để quản lý báo giá trong các tình huống như vậy? ..

Câu trả lời:


35

Xử lý nhiều cấp độ trích dẫn (thực sự, nhiều cấp độ phân tích / giải thích) có thể trở nên phức tạp. Nó giúp ghi nhớ một vài điều:

  • Mỗi cấp độ trích dẫn của Wap có thể liên quan đến một ngôn ngữ khác nhau.
  • Quy tắc trích dẫn thay đổi theo ngôn ngữ.
  • Khi giao dịch với nhiều hơn một hoặc hai cấp độ lồng nhau, thường thì dễ dàng nhất để làm việc từ trên xuống, lên trên (nghĩa là trong cùng đến ngoài cùng).

Cấp độ trích dẫn

Hãy để chúng tôi xem xét các lệnh ví dụ của bạn.

pgrep -fl java | grep -i datanode | awk '{print $1}'

Lệnh ví dụ đầu tiên của bạn (ở trên) sử dụng bốn ngôn ngữ: shell của bạn, regex trong pgrep , regex trong grep (có thể khác với ngôn ngữ regex trong pgrep ) và awk . Có hai cấp độ diễn giải liên quan: shell và một cấp độ sau shell cho mỗi lệnh liên quan. Chỉ có một mức trích dẫn rõ ràng (trích dẫn shell vào awk ).

ssh host 

Tiếp theo bạn đã thêm một mức ssh trên đầu trang. Đây thực sự là một mức vỏ khác: ssh không tự giải thích lệnh, nó trao nó cho một vỏ ở đầu xa (thông qua (ví dụ) sh -c …) và shell đó diễn giải chuỗi.

ssh host "sudo su user -c …"

Sau đó, bạn hỏi về việc thêm một mức vỏ khác ở giữa bằng cách sử dụng su (thông qua sudo , không giải thích các đối số lệnh của nó, vì vậy chúng ta có thể bỏ qua nó). Tại thời điểm này, bạn có ba cấp độ lồng nhau đang diễn ra ( awk → shell, shell → shell ( ssh ), shell → shell ( su user -c ), vì vậy tôi khuyên bạn nên sử dụng phương pháp đáy, lên phương pháp Up. Tôi sẽ giả sử rằng shell của bạn tương thích với Bourne (ví dụ sh , ash , dash , ksh , bash , zsh , v.v.). Một số loại shell khác ( , RC , v.v.) có thể yêu cầu cú pháp khác nhau, nhưng phương pháp vẫn được áp dụng.

Từ dưới lên

  1. Xây dựng chuỗi bạn muốn đại diện ở cấp độ trong cùng.
  2. Chọn một cơ chế trích dẫn từ các tiết mục trích dẫn của ngôn ngữ cao nhất tiếp theo.
  3. Trích dẫn chuỗi mong muốn theo cơ chế trích dẫn đã chọn của bạn.
    • Thường có nhiều biến thể để áp dụng cơ chế trích dẫn nào. Làm bằng tay thường là một vấn đề thực hành và kinh nghiệm. Khi thực hiện theo chương trình, thường là tốt nhất để chọn cách dễ nhất để có được quyền (thường là người giỏi nhất theo nghĩa đen (ít thoát nhất)).
  4. Tùy chọn, sử dụng chuỗi trích dẫn kết quả với mã bổ sung.
  5. Nếu bạn chưa đạt được mức trích dẫn / giải thích mong muốn của mình, hãy lấy chuỗi trích dẫn kết quả (cộng với bất kỳ mã được thêm nào) và sử dụng nó làm chuỗi bắt đầu trong bước 2.

Trích dẫn ngữ nghĩa khác nhau

Điều cần lưu ý ở đây là mỗi ngôn ngữ (mức trích dẫn) có thể đưa ra các ngữ nghĩa hơi khác nhau (hoặc thậm chí khác nhau về ngữ nghĩa) cho cùng một ký tự trích dẫn.

Hầu hết các ngôn ngữ đều có cơ chế trích dẫn chữ viết của người Viking, nhưng chúng khác nhau về chính xác nghĩa đen của chúng. Trích dẫn duy nhất của shell giống như Bourne thực sự là nghĩa đen (có nghĩa là bạn không thể sử dụng nó để trích dẫn một ký tự trích dẫn duy nhất). Ngôn ngữ khác (Perl, Ruby) ít chữ trong đó họ giải thích một số chuỗi xuyệc ngược bên trong vùng trích dẫn đơn không theo nghĩa đen (đặc biệt, \\\'kết quả trong \', nhưng trình tự backslash khác đang thực sự theo nghĩa đen).

Bạn sẽ phải đọc tài liệu cho từng ngôn ngữ của bạn để hiểu các quy tắc trích dẫn và cú pháp tổng thể.

Ví dụ của bạn

Cấp độ trong cùng của ví dụ của bạn là một chương trình awk .

{print $1}

Bạn sẽ nhúng cái này vào dòng lệnh shell:

pgrep -fl java | grep -i datanode | awk 

Chúng ta cần bảo vệ (tối thiểu) không gian và $trong chương trình awk . Sự lựa chọn rõ ràng là sử dụng trích dẫn duy nhất trong shell xung quanh toàn bộ chương trình.

  • '{print $1}'

Có những lựa chọn khác:

  • {print\ \$1} trực tiếp thoát khỏi không gian và $
  • {print' $'1} trích dẫn duy nhất không gian và $
  • "{print \$1}" nhân đôi toàn bộ và thoát khỏi $
  • {print" $"1}trích dẫn kép chỉ khoảng trắng và $
    Điều này có thể uốn cong các quy tắc một chút (không thoát ra $ở cuối chuỗi trích dẫn kép là bằng chữ), nhưng nó dường như hoạt động trong hầu hết các shell.

Nếu chương trình sử dụng dấu phẩy giữa dấu ngoặc nhọn mở và đóng, chúng ta cũng cần trích dẫn hoặc thoát dấu phẩy hoặc dấu ngoặc nhọn để tránh mở rộng niềng răng trong một số vỏ.

Chúng tôi chọn '{print $1}'và nhúng nó vào phần còn lại của shell mã Code:

pgrep -fl java | grep -i datanode | awk '{print $1}'

Tiếp theo, bạn muốn chạy cái này qua susudo .

sudo su user -c 

su user -c …cũng giống như some-shell -c …(ngoại trừ chạy theo một số UID khác), vì vậy su chỉ cần thêm một mức vỏ khác. sudo không diễn giải các đối số của nó, vì vậy nó không thêm bất kỳ mức trích dẫn nào.

Chúng ta cần một mức vỏ khác cho chuỗi lệnh của chúng ta. Chúng tôi có thể chọn trích dẫn một lần nữa, nhưng chúng tôi phải xử lý đặc biệt cho các trích dẫn đơn hiện có. Cách thông thường trông như thế này:

'pgrep -fl java | grep -i datanode | awk '\''{print $1}'\'

Có bốn chuỗi ở đây mà shell sẽ diễn giải và ghép lại: chuỗi trích dẫn đơn đầu tiên ( pgrep … awk), một trích dẫn đơn thoát, chương trình awk trích dẫn đơn , một trích dẫn đơn thoát khác.

Tất nhiên, có nhiều lựa chọn thay thế:

  • pgrep\ -fl\ java\ \|\ grep\ -i\ datanode\ \|\ awk\ \'{print\ \$1} thoát khỏi mọi thứ quan trọng
  • pgrep\ -fl\ java\|grep\ -i\ datanode\|awk\ \'{print\$1}giống nhau, nhưng không có khoảng trắng thừa (ngay cả trong chương trình awk !)
  • "pgrep -fl java | grep -i datanode | awk '{print \$1}'" báo giá gấp đôi toàn bộ, thoát khỏi $
  • 'pgrep -fl java | grep -i datanode | awk '"'"'{print \$1}'"'"biến thể của bạn; dài hơn một chút so với cách thông thường do sử dụng dấu ngoặc kép (hai ký tự) thay vì thoát (một ký tự)

Sử dụng trích dẫn khác nhau ở cấp độ đầu tiên cho phép các biến thể khác ở cấp độ này:

  • 'pgrep -fl java | grep -i datanode | awk "{print \$1}"'
  • 'pgrep -fl java | grep -i datanode | awk {print\ \$1}'

Nhúng biến thể đầu tiên trong dòng lệnh sudo / * su * đưa ra điều này:

sudo su user -c 'pgrep -fl java | grep -i datanode | awk '\''{print $1}'\'

Bạn có thể sử dụng cùng một chuỗi trong bất kỳ bối cảnh cấp vỏ đơn nào khác (ví dụ ssh host …).

Tiếp theo, bạn đã thêm một mức ssh trên đầu trang. Đây thực sự là một mức vỏ khác: ssh không tự giải thích lệnh, nhưng nó trao nó cho một vỏ ở đầu từ xa (thông qua (ví dụ) sh -c …) và shell đó diễn giải chuỗi.

ssh host 

Quá trình này giống nhau: lấy chuỗi, chọn phương thức trích dẫn, sử dụng nó, nhúng nó.

Sử dụng dấu ngoặc đơn một lần nữa:

'sudo su user -c '\''pgrep -fl java | grep -i datanode | awk '\'\\\'\''{print $1}'\'\\\'

Bây giờ có mười một chuỗi được giải thích và nối: 'sudo su user -c ', thoát dấu nháy đơn, 'pgrep … awk ', thoát dấu nháy đơn, dấu chéo ngược trốn thoát, hai thoát dấu nháy đơn, các trích dẫn đơn awk chương trình, một thoát quote duy nhất, một dấu chéo ngược trốn thoát, và một thức thoát khỏi dấu nháy đơn .

Mẫu cuối cùng trông như thế này:

ssh host 'sudo su user -c '\''pgrep -fl java | grep -i datanode | awk '\'\\\'\''{print $1}'\'\\\'

Điều này hơi khó sử dụng để gõ bằng tay, nhưng tính chất nghĩa đen của trích dẫn đơn của shell giúp dễ dàng tự động hóa một biến thể nhỏ:

#!/bin/sh

sq() { # single quote for Bourne shell evaluation
    # Change ' to '\'' and wrap in single quotes.
    # If original starts/ends with a single quote, creates useless
    # (but harmless) '' at beginning/end of result.
    printf '%s\n' "$*" | sed -e "s/'/'\\\\''/g" -e 1s/^/\'/ -e \$s/\$/\'/
}

# Some shells (ksh, bash, zsh) can do something similar with %q, but
# the result may not be compatible with other shells (ksh uses $'...',
# but dash does not recognize it).
#
# sq() { printf %q "$*"; }

ap='{print $1}'
s1="pgrep -fl java | grep -i datanode | awk $(sq "$ap")"
s2="sudo su user -c $(sq "$s1")"

ssh host "$(sq "$s2")"

5
Giải thích tuyệt vời!
Gilles 'SO- ngừng trở nên xấu xa'

7

Xem câu trả lời của Chris Johnsen cho một lời giải thích rõ ràng, sâu sắc với một giải pháp chung. Tôi sẽ đưa ra một vài lời khuyên bổ sung có ích trong một số trường hợp phổ biến.

Trích dẫn duy nhất thoát khỏi tất cả mọi thứ, nhưng một trích dẫn duy nhất. Vì vậy, nếu bạn biết giá trị của một biến không bao gồm bất kỳ trích dẫn nào, bạn có thể nội suy nó một cách an toàn giữa các trích dẫn đơn trong tập lệnh shell.

su -c "grep '$pattern' /root/file"  # assuming there is no ' in $pattern

Nếu shell cục bộ của bạn là ksh93 hoặc zsh, bạn có thể đối phó với các dấu ngoặc đơn trong biến bằng cách viết lại chúng thành '\''. (Mặc dù bash cũng có ${foo//pattern/replacement}cấu trúc, việc xử lý các trích dẫn đơn lẻ không có ý nghĩa với tôi.)

su -c "grep '${pattern//'/'\''}' /root/file"  # if the outer shell is zsh
su -c "grep '${pattern//\'/\'\\\'\'}' /root/file"  # if the outer shell is ksh93

Một mẹo khác để tránh phải đối phó với trích dẫn lồng nhau là chuyển các chuỗi qua các biến môi trường càng nhiều càng tốt. Ssh và sudo có xu hướng loại bỏ hầu hết các biến môi trường, nhưng chúng thường được cấu hình để cho LC_*qua, bởi vì chúng thường rất quan trọng đối với khả năng sử dụng (chúng chứa thông tin ngôn ngữ) và hiếm khi được coi là nhạy cảm bảo mật.

LC_CMD='what you would use locally' ssh $host 'sudo su user -c "$LC_CMD"'

Ở đây, vì LC_CMDchứa một đoạn vỏ, nó phải được cung cấp theo nghĩa đen cho lớp vỏ trong cùng. Do đó, biến được mở rộng bằng vỏ ngay trên. Vỏ trong cùng nhìn thấy một "$LC_CMD", và vỏ trong cùng nhìn thấy các lệnh.

Một phương pháp tương tự rất hữu ích để truyền dữ liệu đến một tiện ích xử lý văn bản. Nếu bạn sử dụng phép nội suy shell, tiện ích sẽ coi giá trị của biến là một lệnh, ví dụ: sed "s/$pattern/$replacement/"sẽ không hoạt động nếu các biến chứa /. Vì vậy, sử dụng awk (không phải sed) và -vtùy chọn của nó hoặc ENVIRONmảng để truyền dữ liệu từ shell (nếu bạn đi qua ENVIRON, hãy nhớ xuất các biến).

awk -vpattern="$pattern" replacement="$replacement" '{gsub(pattern,replacement); print}'

2

Như Chris Johnson mô tả rất tốt , bạn có một vài mức trích dẫn không xác định ở đây; bạn chỉ thị cho địa phương của bạn shellđể hướng dẫn điều khiển từ xa shellthông qua sshđó nên hướng sudodẫn suđể hướng dẫn điều khiển từ xa shellđể chạy đường ống của bạn pgrep -fl java | grep -i datanode | awk '{print $1}'như user. Loại lệnh đó đòi hỏi rất nhiều tẻ nhạt \'"quote quoting"\'.

Nếu bạn nghe theo lời khuyên của tôi, bạn sẽ từ bỏ tất cả những điều vô nghĩa và làm:

% ssh $host <<REM=LOC_EXPANSION <<'REMOTE_CMD' |
> localhost_data='$(commands run on localhost at runtime)' #quotes don't affect expansion
> more_localhost_data="$(values set at heredoc expansion)" #remote shell will receive m_h_d="result"
> REM=LOC_EXPANSION
> commands typed exactly as if located at 
> the remote terminal including variable 
> "${{more_,}localhost_data}" operations
> 'quotes and' \all possibly even 
> a\wk <<'REMOTELY_INVOKED_HEREDOC' |
> {as is often useful with $awk
> so long as the terminator for}
> REMOTELY_INVOKED_HEREDOC
> differs from that of REM=LOC_EXPANSION and
> REMOTE_CMD
> and here you can | pipeline operate on |\
> any output | received from | ssh as |\
> run above | in your local | terminal |\
> however | tee > ./you_wish.result
<desired output>

ĐỂ BIẾT THÊM:

Kiểm tra câu trả lời (có lẽ quá dài dòng) của tôi đối với đường dẫn Piping với các loại trích dẫn khác nhau để thay thế dấu gạch chéo trong đó tôi thảo luận về một số lý thuyết đằng sau lý do tại sao điều đó hoạt động.

-Như


Điều này có vẻ thú vị, nhưng tôi không thể làm cho nó hoạt động. Bạn có thể gửi một ví dụ làm việc tối thiểu?
John Lawrence Aspden

Tôi tin rằng ví dụ này yêu cầu zsh vì nó sử dụng nhiều chuyển hướng để stdin. Trong các vỏ giống như Bourne khác, cái thứ hai <<chỉ đơn giản là thay thế cái thứ nhất. Nó nên nói "zsh only" ở đâu đó, hay tôi đã bỏ lỡ điều gì? (Mặc dù vậy, mẹo thông minh để có một di sản là một phần của việc mở rộng cục bộ)

Đây là phiên bản tương thích với bash: unix.stackexchange.com/questions/422361/iêu
dabest1

0

Làm thế nào về việc sử dụng nhiều dấu ngoặc kép?

Sau đó, bạn ssh $host $CMDnên làm việc tốt với cái này:

CMD="pgrep -fl java | grep -i datanode | awk '{print $1}'"

Bây giờ với một phức tạp hơn, các ssh $host "sudo su user -c \"$CMD\"". Tôi đoán tất cả những gì bạn phải làm là thoát khỏi các nhân vật nhạy cảm trong CMD: $, \". Vì vậy, tôi sẽ thử và xem nếu điều này làm việc : echo $CMD | sed -e 's/[$\\"]/\\\1/g'.

Nếu điều đó có vẻ ổn, hãy bọc echo + sed vào một hàm shell, và bạn tốt để đi cùng ssh $host "sudo su user -c \"$(escape_my_var $CMD)\"".

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.