Chương trình có thể nhận được số khoảng trắng giữa các đối số dòng lệnh trong POSIX không?


23

Nói nếu tôi đã viết một chương trình với dòng sau:

int main(int argc, char** argv)

Bây giờ nó biết những đối số dòng lệnh nào được truyền cho nó bằng cách kiểm tra nội dung của argv.

Chương trình có thể phát hiện có bao nhiêu khoảng cách giữa các đối số không? Giống như khi tôi gõ những thứ này trong bash:

ibug@linux:~ $ ./myprog aaa bbb
ibug@linux:~ $ ./myprog       aaa      bbb

Môi trường là một Linux hiện đại (như Ubuntu 16.04), nhưng tôi cho rằng câu trả lời nên áp dụng cho bất kỳ hệ thống nào tuân thủ POSIX.


22
Chỉ vì tò mò, tại sao chương trình của bạn cần phải biết điều đó?
nxnev 15/03/18

2
@nxnev Tôi đã từng viết một số chương trình Windows và tôi biết nó có thể ở đó, vì vậy tôi tự hỏi liệu có thứ gì đó tương tự trong Linux (hoặc Unix) không.
iBug

9
Tôi mơ hồ nhớ trong CP / M rằng các chương trình phải phân tích các dòng lệnh của riêng chúng - điều này có nghĩa là mọi thời gian chạy C đều phải thực hiện trình phân tích cú pháp shell. Và tất cả họ đã làm điều đó một chút khác nhau.
Toby Speight

3
@iBug Có, nhưng bạn cần trích dẫn các đối số khi gọi lệnh. Đó là cách nó được thực hiện trên vỏ POSIX (và tương tự).
Konrad Rudolph

3
@iBug, ... Windows có cùng thiết kế mà Toby đề cập từ CP / M ở trên. UNIX không làm điều đó - từ quan điểm được gọi là quá trình, có không có dòng lệnh có liên quan trong việc điều hành nó.
Charles Duffy

Câu trả lời:


39

Thật không có ý nghĩa khi nói về "khoảng cách giữa các đối số"; đó là một khái niệm vỏ.

Công việc của shell là lấy toàn bộ các dòng đầu vào và tạo chúng thành các mảng đối số để bắt đầu các lệnh với. Điều này có thể liên quan đến phân tích chuỗi trích dẫn, mở rộng biến, ký tự đại diện và biểu thức dấu ngã, v.v. Lệnh được bắt đầu với một execcuộc gọi hệ thống tiêu chuẩn , chấp nhận một vectơ của chuỗi.

Các cách khác tồn tại để tạo ra một vectơ của chuỗi. Nhiều chương trình rẽ nhánh và thực hiện các quy trình con của riêng chúng với các yêu cầu lệnh được xác định trước - trong trường hợp đó, không bao giờ có thứ gọi là "dòng lệnh". Tương tự, trình bao đồ họa (máy tính để bàn) có thể bắt đầu một quá trình khi người dùng kéo biểu tượng tệp và thả nó vào tiện ích lệnh - một lần nữa, không có dòng văn bản nào có các ký tự "giữa" đối số.

Theo như lệnh được gọi, những gì diễn ra trong shell hoặc quá trình cha / tiền thân khác là riêng tư và ẩn - chúng ta chỉ thấy mảng các chuỗi mà tiêu chuẩn C chỉ định main()có thể chấp nhận.


Câu trả lời hay - điều quan trọng là chỉ ra điều này cho những người mới sử dụng Unix, những người thường cho rằng, nếu họ chạy tar cf texts.tar *.txtthì chương trình tar nhận được hai đối số và phải tự mở rộng cái thứ hai ( *.txt). Nhiều người không nhận ra nó thực sự hoạt động như thế nào cho đến khi họ bắt đầu viết các kịch bản / chương trình riêng để xử lý các đối số.
Laurence Renshaw

58

Nói chung, không. Phân tích cú pháp dòng lệnh được thực hiện bởi shell mà không làm cho dòng không có sẵn có sẵn cho chương trình được gọi. Trong thực tế, chương trình của bạn có thể được thực thi từ một chương trình khác tạo ra argv không phải bằng cách phân tích một chuỗi mà bằng cách xây dựng một mảng các đối số theo lập trình.


9
Bạn có thể muốn đề cập execve(2).
iBug

3
Bạn nói đúng, như một cái cớ khập khiễng tôi có thể nói rằng tôi hiện đang sử dụng điện thoại và tìm kiếm các trang người đàn ông hơi tẻ nhạt :-)
Hans-Martin Mosner 15/03/18

1
Đây là phần có liên quan của POSIX.
Stephen Kitt

1
@ Hans-MartinMosner: Termux ...? ;-)
DevSolar

9
"Nói chung" có nghĩa là một biện pháp bảo vệ chống lại việc trích dẫn một trường hợp đặc biệt có thể xảy ra - ví dụ, một quy trình gốc suid có thể có thể kiểm tra bộ nhớ của vỏ gọi và tìm chuỗi dòng lệnh không được xử lý.
Hans-Martin Mosner 16/03/18

16

Không, điều này là không thể, trừ khi các khoảng trắng là một phần của một đối số.

Lệnh truy cập các đối số riêng lẻ từ một mảng (dưới dạng này hay dạng khác tùy thuộc vào ngôn ngữ lập trình) và dòng lệnh thực tế có thể được lưu vào tệp lịch sử (nếu được nhập tại dấu nhắc tương tác trong trình bao có tệp lịch sử), nhưng không bao giờ truyền lại cho lệnh dưới mọi hình thức.

Tất cả các lệnh trên Unix cuối cùng được thực thi bởi một trong các exec()họ hàm. Chúng lấy tên lệnh và một danh sách hoặc mảng đối số. Không ai trong số họ nhận một dòng lệnh như được gõ tại dấu nhắc shell. Các system()chức năng thực hiện, nhưng đối số chuỗi của nó được thực hiện bởi sau execve(), trong đó, một lần nữa, phải mất một mảng các đối số chứ không phải là một chuỗi dòng lệnh.


2
@LightnessRacesinOrbit Tôi đặt nó trong đó chỉ trong trường hợp có một số nhầm lẫn về "khoảng cách giữa các đối số". Đưa không gian trong dấu ngoặc kép giữa helloworldnghĩa đen không gian giữa hai đối số.
Kusalananda

5
@Kusalananda - Vâng, không ... Đưa không gian trong dấu ngoặc kép giữa helloworldđược theo nghĩa đen cung cấp thứ hai trong ba đối số.
Jeremy

@Jeremy Như tôi đã nói, trong trường hợp có bất kỳ sự nhầm lẫn nào về ý nghĩa của "giữa các đối số". Vâng, như là một cuộc tranh luận thứ hai giữa hai người kia nếu bạn muốn.
Kusalananda

Ví dụ của bạn là tốt, và hướng dẫn.
Jeremy

1
Vâng, các bạn, các ví dụ là một nguồn rõ ràng của sự nhầm lẫn và hiểu lầm. Tôi đã xóa chúng vì không thêm vào giá trị của câu trả lời.
Kusalananda

9

Nói chung, không thể, như một số câu trả lời khác được giải thích.

Tuy nhiên, vỏ Unixbình thường chương trình (và họ đang giải thích các dòng lệnh và globbing nó, tức là mở rộng lệnh trước khi thực hiện fork& execvecho nó). Xem giải thíchbash này về hoạt động vỏ . Bạn có thể viết shell của riêng bạn (hoặc bạn có thể vá một số shell phần mềm miễn phí hiện có , ví dụ GNU bash ) và sử dụng nó làm shell của bạn (hoặc thậm chí cả shell đăng nhập của bạn, xem passwd (5) & shells (5) ).

Ví dụ, bạn có thể có của bạn chương trình shell riêng đặt dòng lệnh đầy đủ trong một số biến môi trường (tưởng tượng MY_COMMAND_LINEví dụ) -Hoặc sử dụng bất kỳ loại khác của quá trình liên lạc để truyền tải dòng lệnh từ vỏ đến process- đứa trẻ.

Tôi không hiểu tại sao bạn muốn làm điều đó, nhưng bạn có thể mã hóa một cái vỏ hành xử theo cách như vậy (nhưng tôi khuyên bạn không nên làm như vậy).

BTW, một chương trình có thể được khởi động bởi một số chương trình không phải là shell (nhưng thực hiện fork (2) sau đó thực thi (2) hoặc chỉ là execveđể bắt đầu một chương trình trong quy trình hiện tại của nó). Trong trường hợp đó, không có dòng lệnh nào cả, và chương trình của bạn có thể được khởi động mà không cần lệnh ...

Lưu ý rằng bạn có thể có một số hệ thống Linux (chuyên dụng) mà không cần cài đặt shell. Điều này là kỳ lạ và bất thường, nhưng có thể. Sau đó, bạn sẽ cần phải viết một chương trình init chuyên biệt bắt đầu các chương trình khác khi cần - không sử dụng bất kỳ shell nào bằng cách thực hiện fork& execvegọi hệ thống.

Đọc thêm Hệ điều hành: Ba phần dễ dàng và đừng quên rằng execvethực tế luôn luôn là một cuộc gọi hệ thống (trên Linux, chúng được liệt kê trong các tòa nhà (2) , xem thêm phần giới thiệu (2) ) để xác định lại không gian địa chỉ ảo (và một số khác điều) của quá trình làm điều đó.


Đây là câu trả lời tốt nhất. Tôi giả sử (không cần nhìn lên) rằng argv[0] tên chương trình và các yếu tố còn lại cho các đối số là thông số kỹ thuật POSIX và không thể thay đổi. Một môi trường thời gian chạy có thể chỉ định argv[-1]cho dòng lệnh, tôi giả sử ...
Peter - Tái lập lại Monica

Không, nó không thể. Đọc kỹ execvetài liệu hướng dẫn. Bạn không thể sử dụng argv[-1], đó là hành vi không xác định để sử dụng nó.
Basile Starynkevitch 17/03/18

Vâng, điểm tốt (cũng là gợi ý rằng chúng tôi có một tòa nhà cao tầng) - ý tưởng là một chút giả định. Tất cả ba thành phần của thời gian chạy (shell, stdlib và OS) sẽ cần phải cộng tác. Shell cần gọi một hàm không phải là POSIX đặc biệt execvepluscmdvới một tham số phụ (hoặc quy ước argv), tòa nhà chọc trời xây dựng một vectơ đối số cho chính có chứa một con trỏ tới dòng lệnh trước con trỏ tới tên chương trình, rồi truyền địa chỉ của con trỏ tới tên chương trình như argvkhi gọi chương trình main...
Peter - Phục hồi Monica

Không cần phải viết lại vỏ, chỉ cần sử dụng dấu ngoặc kép. Tính năng này đã có sẵn từ vỏ bẻ khóa sh. Vì vậy, không phải là mới.
ctrl-alt-delor 18/03/18

Sử dụng dấu ngoặc kép để thay đổi dòng lệnh. Và OP không muốn điều đó
Basile Starynkevitch 18/03/18

3

Bạn luôn có thể nói với shell của bạn để cho các ứng dụng biết mã shell nào dẫn đến việc thực thi chúng. Chẳng hạn, với zsh, bằng cách chuyển thông tin đó trong $SHELL_CODEbiến môi trường bằng cách sử dụng preexec()hook ( printenvđược sử dụng làm ví dụ, bạn sẽ sử dụng getenv("SHELL_CODE")trong chương trình của mình):

$ preexec() export SHELL_CODE=$1
$ printenv SHELL_CODE
printenv SHELL_CODE
$ printenv  SHELL_CODE
printenv  CODE
$ $(echo printenv SHELL_CODE)
$(echo printenv SHELL_CODE)
$ for i in SHELL_CODE; do printenv "$i"; done
for i in SHELL_CODE; do printenv "$i"; done
$ printenv SHELL_CODE; : other command
printenv SHELL_CODE; : other command
$ f() printenv SHELL_CODE
$ f
f

Tất cả những người sẽ thực hiện printenvnhư:

execve("/usr/bin/printenv", ["printenv", "SHELL_CODE"], 
       ["PATH=...", ..., "SHELL_CODE=..."]);

Cho phép printenvtruy xuất mã zsh dẫn đến việc thực thi printenvvới các đối số đó. Những gì bạn muốn làm với thông tin đó là không rõ ràng với tôi.

Với bash, tính năng gần zsh's preexec()sẽ sử dụng nó $BASH_COMMANDtrong một DEBUGcái bẫy, nhưng lưu ý rằng bashhiện một số mức độ viết lại ở đó (và trong refactors đặc biệt một số các khoảng trắng dùng làm dấu phân cách) và của áp dụng cho mỗi (tốt, một số) lệnh chạy, không phải toàn bộ dòng lệnh như được nhập tại dấu nhắc (xem thêm functracetùy chọn).

$ trap 'export SHELL_CODE="$BASH_COMMAND"' DEBUG
$ printenv SHELL_CODE
printenv SHELL_CODE
$ printenv $(echo 'SHELL_CODE')
printenv $(echo 'SHELL_CODE')
$ for i in SHELL_CODE; do printenv "$i"; done; : other command
printenv "$i"
$ printf '%s\n' "$(printenv "SHELL_CODE")"
printf '%s\n' "$(printenv "SHELL_CODE")"
$ set -o functrace
$ printf '%s\n' "$(printenv "SHELL_CODE")"
printenv "SHELL_CODE"
$ print${-+env  }    $(echo     'SHELL_CODE')
print${-+env  } $(echo     'SHELL_CODE')

Xem làm thế nào một số khoảng trắng là dấu phân cách trong cú pháp ngôn ngữ shell đã được nén thành 1 và làm thế nào không phải dòng lệnh đầy đủ không luôn luôn được truyền cho lệnh. Vì vậy, có lẽ không hữu ích trong trường hợp của bạn.

Lưu ý rằng tôi sẽ không khuyên bạn thực hiện loại điều này, vì bạn có khả năng rò rỉ thông tin nhạy cảm cho mọi lệnh như trong:

echo very_secret | wc -c | untrustedcmd

sẽ rò rỉ bí mật đó cho cả hai wcuntrustedcmd.

Tất nhiên, bạn có thể làm điều đó cho các ngôn ngữ khác ngoài shell. Ví dụ: trong C, bạn có thể sử dụng một số macro xuất mã C thực thi lệnh ra môi trường:

#include <unistd.h>
#include <stdlib.h>
#include <sys/wait.h>
#define WRAP(x) (setenv("C_CODE", #x, 1), x)

int main(int argc, char *argv[])
{
  if (!fork()) WRAP(execlp("printenv", "printenv", "C_CODE", NULL));
  wait(NULL);
  if (!fork()) WRAP(0 + execlp("printenv",   "printenv", "C_CODE", NULL));
  wait(NULL);
  if (argc > 1 && !fork()) WRAP(execvp(argv[1], &argv[1]));
  wait(NULL);
  return 0;
}

Thí dụ:

$ ./a.out printenv C_CODE
execlp("printenv", "printenv", "C_CODE", NULL)
0 + execlp("printenv", "printenv", "C_CODE", NULL)
execvp(argv[1], &argv[1])

Xem cách một số khoảng trắng được bộ xử lý trước C ngưng tụ như trong trường hợp bash. Trong hầu hết nếu không phải tất cả các ngôn ngữ, lượng không gian được sử dụng trong các dấu phân cách không có sự khác biệt, vì vậy không có gì đáng ngạc nhiên khi trình biên dịch / trình thông dịch có một số quyền tự do với chúng ở đây.


Khi tôi đang thử nghiệm điều này, BASH_COMMANDkhông chứa các đối số phân tách khoảng trắng ban đầu, vì vậy điều này không thể sử dụng được cho yêu cầu theo nghĩa đen của OP. Câu trả lời này có bao gồm bất kỳ cách trình diễn nào cho trường hợp sử dụng cụ thể đó không?
Charles Duffy

@CharlesDuffy, tôi chỉ muốn chỉ ra tương đương gần nhất với preexec () của zsh trong bash (vì đó là cái vỏ mà OP đang giới thiệu) và chỉ ra rằng nó không thể được sử dụng cho trường hợp sử dụng cụ thể đó, nhưng tôi đồng ý rằng nó không phải là rất rõ ràng. Xem chỉnh sửa. Câu trả lời này nhằm mục đích chung chung hơn về cách vượt qua mã nguồn (ở đây là zsh / bash / C) khiến cho việc thực thi lệnh được thực thi (không phải là điều hữu ích, nhưng tôi hy vọng rằng trong khi làm như vậy, và đặc biệt là với các ví dụ, tôi chứng minh rằng nó không hữu ích lắm)
Stéphane Chazelas 18/03/18

0

Tôi sẽ chỉ thêm những gì còn thiếu trong các câu trả lời khác.

Không

Xem câu trả lời khác

Có lẽ, sắp xếp

Không có gì có thể được thực hiện trong chương trình, nhưng có một cái gì đó có thể được thực hiện trong trình bao khi bạn chạy chương trình.

Bạn cần sử dụng dấu ngoặc kép. Vì vậy, thay vì

./myprog      aaa      bbb

bạn cần phải làm một trong những

./myprog "     aaa      bbb"
./myprog '     aaa      bbb'

Điều này sẽ truyền một đối số duy nhất cho chương trình, với tất cả các khoảng trắng. Có một sự khác biệt giữa hai, thứ hai là bằng chữ, chính xác là chuỗi như nó xuất hiện (ngoại trừ 'phải được gõ là \'). Người đầu tiên sẽ giải thích một số ký tự, nhưng chia thành nhiều đối số. Xem trích dẫn vỏ để biết thêm thông tin. Vì vậy, không cần phải viết lại vỏ, các nhà thiết kế vỏ đã nghĩ về điều đó. Tuy nhiên vì bây giờ là một đối số, bạn sẽ phải thực hiện nhiều hơn trong chương trình.

Lựa chọn 2

Truyền dữ liệu qua stdin. Đây là cách thông thường để có được lượng lớn dữ liệu vào một lệnh. ví dụ

./myprog << EOF
    aaa      bbb
EOF

hoặc là

./myprog
Tell me what you want to tell me:
aaaa bbb
ctrl-d

(Chữ nghiêng là đầu ra của chương trình)


Về mặt kỹ thuật, mã shell: ./myprog␣"␣␣␣␣␣aaa␣␣␣␣␣␣bbb"thực thi (nói chung là trong một tiến trình con), tệp được lưu trữ ./myprogvà truyền cho nó hai đối số: ./myprog␣␣␣␣␣aaa␣␣␣␣␣␣bbb( argv[0]argc[1], argclà 2) và như trong OP, không gian phân tách hai đối số đó không được truyền theo bất kỳ cách nào để myprog.
Stéphane Chazelas 18/03/18

Nhưng bạn đang thay đổi lệnh, và OP không muốn thay đổi nó
Basile Starynkevitch

@BasileStarynkevitch Sau bình luận của bạn, tôi đọc lại câu hỏi. Bạn đang đưa ra một giả định. Không nơi nào OP nói rằng họ không muốn thay đổi cách chương trình được chạy. Có lẽ điều này là đúng, nhưng họ không có gì để nói về nó. Do đó, câu trả lời này có thể là những gì họ cần.
ctrl-alt-delor 18/03/18

OP hỏi rõ ràng về khoảng trắng giữa các đối số, không phải về một đối số duy nhất chứa khoảng trắng
Basile Starynkevitch
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.