Linux
其實 C 函式庫中已經宣告好 3 個 FILE *
的指標,分別是 stdin
、stdout
、stderr
。
我們常用的函式 printf
、scanf
,實際上是包裝過後的結果:
一般情況下,fprintf、printf 執行的結果是相同的。
當你到 /dev
中下 ls
會發現有許多熟悉的東西,舉凡 Console、SSH、Socket…等,每個你能想到的幾乎都能在這裡被找到。
而在 UNIX 當中有句話 "Everything is a file",描述了 Unix 及其衍生產品的定義特性之一,意思表示廣泛的輸入/輸出資源,如 File、目錄、硬體驅動、Modems、鍵盤、印表機,甚至一些 Process 間和網路通訊都是透過 File System 命名空間公開的簡單資料流。
這種方法的優點是可以在廣泛的資源上使用同一組工具、實用程式和 API。有多種檔案類型。打開檔案時,會創建 file descriptor。該路徑成為尋址系統和 file descriptor 是之間串流 I/O 的介面接口(interface)。但是 file descriptor 也是通過不同的方法為匿名 Pipe 和 Network Socket 等創建的。因此對這個特性更準確的描述是 Everything is a file descriptor
。
所以 Shell 是怎樣跑一個程式的呢?
pid 0
)接到指令,比方說 ls 好了,他就會先 fork() 出一個新 process(pid 1
),然後新的 pid 1
使用 exec 指令將 forked 的 Shell 取代成 ls 並執行。此時 Shell(pid 0
)會用 waitpid() 等 ls(pid 1
)執行完印出輸出,才繼續執行 Shell。Shell:
$ # pid 0
-----------------------------------------------------
$ ls # pid 0 fork() 出 pid 1
A B C D # pid 1 執行 ls
-----------------------------------------------------
$ # pid 0 用 waitpid() 等 pid 1 結束才繼續
pipe()
這個 System Call,他是一種讓程序之間可以溝通的方式之一,在實作 Shell 時,我們會需要用 pipe()
和 dup2()
來搞定 A | B。請大家先看「pipe()
System call」、「C program to demonstrate fork()
and pipe()
」和「dup()
and dup2()
Linux system call」這三段,再接下去看下面的範例。pipe()
System callpipe
又稱 pipeline
,屬於系統調用的一種,用於兩個 Process 之間的溝通,通常是從一個 Process 的 stdout 送到另一個 Process 的 stdin。在 UNIX 系統中,Pipeline 對於 Process 之間的通訊是非常有用的。虛擬文件
呢? 創建該文件的 Process 及其 Subprocess 皆擁有該文件的讀寫權限。pipe()
系統調用將會在 Process 打開的文件列表 (file descriptor table) 中找到前兩個可用的位置,並將它們分配給 Pipeline 讀寫端使用。int pipe(int fds[2]);
Parameters :
fd[0] will be the fd(file descriptor) for the read end of pipe.
fd[1] will be the fd for the write end of pipe.
Returns : 0 on Success.
-1 on error.
// C program to illustrate
// pipe system call in C
#include <stdio.h>
#include <unistd.h>
#define MSGSIZE 16
char* msg1 = "hello, world #1";
char* msg2 = "hello, world #2";
char* msg3 = "hello, world #3";
int main()
{
char inbuf[MSGSIZE];
int p[2], i;
if (pipe(p) < 0)
exit(1);
/* continued */
/* write pipe */
write(p[1], msg1, MSGSIZE);
write(p[1], msg2, MSGSIZE);
write(p[1], msg3, MSGSIZE);
for (i = 0; i < 3; i++) {
/* read pipe */
read(p[0], inbuf, MSGSIZE);
printf("% s\n", inbuf);
}
return 0;
}
fork()
之前有 pipe()
的話,將能透過此 Pipeline 進行溝通。fork()
and pipe()
"fork()
and pipe()
資料來源
在此撰寫 Linux C 程式來創建兩個 Process,P1 和 P2。
另一個字串:forgeeks.org
P1 輸入:www.geeks
P1 輸出:www.geeksforgeeks.org
P1 輸入:www.practice.geeks
P1 輸出:practice.geeksforgeeks.org
fork()
使用說明#include <unistd.h>
...
pid_t fork(void);
fork()
,而 return 的意義如下:
0 : 即子行程的 PID。當 > 0 時,表示目前正在執行父行程當中執行,而父行程知道子行程的 PID。
// C program to demonstrate use of fork() and pipe()
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<sys/types.h>
#include<string.h>
#include<sys/wait.h>
int main()
{
// We use two pipes
// First pipe to send input string from parent
// Second pipe to send concatenated string from child
int fd1[2]; // Used to store two ends of first pipe
int fd2[2]; // Used to store two ends of second pipe
char fixed_str[] = "forgeeks.org";
char input_str[100];
pid_t p;
if (pipe(fd1)==-1)
{
fprintf(stderr, "Pipe Failed" );
return 1;
}
if (pipe(fd2)==-1)
{
fprintf(stderr, "Pipe Failed" );
return 1;
}
printf("Please input a string:");
scanf("%s", input_str);
p = fork();
if (p < 0)
{
fprintf(stderr, "fork Failed" );
return 1;
}
// Parent process
else if (p > 0)
{
char concat_str[100];
close(fd1[0]); // Close reading end of first pipe
// Write input string and close writing end of first
// pipe.
write(fd1[1], input_str, strlen(input_str)+1);
close(fd1[1]);
// Wait for child to send a string
wait(NULL);
close(fd2[1]); // Close writing end of second pipe
// Read string from child, print it and close
// reading end.
read(fd2[0], concat_str, 100);
printf("Concatenated string %s\n", concat_str);
close(fd2[0]);
}
// child process
else
{
close(fd1[1]); // Close writing end of first pipe
// Read a string using first pipe
char concat_str[100];
read(fd1[0], concat_str, 100);
// Concatenate a fixed string with it
int k = strlen(concat_str);
int i;
for (i=0; i<strlen(fixed_str); i++)
concat_str[k++] = fixed_str[i];
concat_str[k] = '\0'; // string ends with '\0'
// Close both reading ends
close(fd1[0]);
close(fd2[0]);
// Write concatenated string and close writing end
write(fd2[1], concat_str, strlen(concat_str)+1);
close(fd2[1]);
exit(0);
}
}
dup()
and dup2()
Linux system call資料來源
dup()
及 dup2()
為系統呼叫,用於複製 file descriptor。
offset
及 檔案的狀態旗標
將會是一樣的。int dup(int oldfd);
oldfd: old file descriptor whose copy is to be created.
// CPP program to illustrate dup()
#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
int main()
{
// open() returns a file descriptor file_desc to a
// the file "dup.txt" here"
int file_desc = open("./dup.txt", O_WRONLY | O_APPEND | O_CREAT);
if(file_desc < 0)
printf("Error opening the file\n");
// dup() will create the copy of file_desc as the copy_desc
// then both can be used interchangeably.
int copy_desc = dup(file_desc);
// write() will write the given string into the file
// referred by the file descriptors
write(copy_desc,"This will be output to the file named dup.txt\n", 46);
write(file_desc,"This will also be output to the file named dup.txt\n", 51);
system("cat ./dup.txt");
system("rm -rf ./dup.txt");
return 0;
}
// shell.cpp
#include <errno.h>
#include <fcntl.h>
#include <iostream>
#include <signal.h>
#include <stdio.h>
#include <sys/wait.h>
#include <unistd.h>
int main(int argc, char **argv) {
// 處理 SIGCHLD,可以避免 Child 疆屍程序
struct sigaction sigchld_action;
sigchld_action.sa_handler = SIG_DFL;
sigchld_action.sa_flags = SA_NOCLDWAIT;
// 原本指令 ls | cat | cat | cat | cat | cat | cat | cat | cat
// 假設 Shell 已經將指令 Parse 好
char **cmds[9];
char *p1_args[] = {"ls", NULL};
cmds[0] = p1_args;
char *p2_args[] = {"cat", NULL}; // 只是 DEMO,所以重複利用
for (int i = 1; i < 9; i++)
cmds[i] = p2_args;
int pipes[16]; // 需要共 8 條 pipe
for (int i = 0; i < 8; i++)
pipe(pipes + i * 2); // 建立 i-th pipe
pid_t pid;
for (int i = 0; i < 9; i++) {
pid = fork();
if (pid == 0) { // Child
// 讀取端
if (i != 0) {
// 用 dup2 將 pipe 讀取端取代成 stdin
dup2(pipes[(i - 1) * 2], STDIN_FILENO);
}
// 用 dup2 將 pipe 寫入端取代成 stdout
if (i != 8) {
dup2(pipes[i * 2 + 1], STDOUT_FILENO);
}
// 關掉之前一次打開的
for (int j = 0; j < 16; j++) {
close(pipes[j]);
}
execvp(*cmds[i], cmds[i]);
// execvp 正確執行的話,程式不會繼續到這裡
fprintf(stderr, "Cannot run %s\n", *cmds[i]);
} else { // Parent
printf("- fork %d\n", pid);
if (i != 0) {
close(pipes[(i - 1) * 2]); // 前一個的寫
close(pipes[(i - 1) * 2 + 1]); // 當前的讀
}
}
}
waitpid(pid, NULL, 0); // 等最後一個指令結束
std::cout << "===" << std::endl;
std::cout << "All done." << std::endl;
}
$ g++ shell.cpp && ./a.out
- fork 8244
- fork 8245
- fork 8246
- fork 8247
- fork 8248
- fork 8249
- fork 8250
- fork 8251
- fork 8252
FILE_A
FILE_B
FILE_C
===
All done.
$ bash --version
GNU bash, version 4.3.48(1)-release (x86_64-pc-linux-gnu)
Copyright (C) 2013 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
This is free software; you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.
Bash 的命令語法是 Bourne shell 命令語法的超集。數量龐大的 Bourne shell 指令碼大多不經修改即可以在 bash 中執行,也因此 bash 成為許多 Linux Base 發行版預設使用的 Shell。
能夠做到排列組合的功效,但不建議在可移植的環境當中使用,可能會在其他 Shell 中造成結果上的不同。
$ echo a{p,c,d,b}e
ape ace ade abe
$ echo {a,b,c}{d,e,f}
ad ae af bd be bf cd ce cf
可以直接做計算
VAR=55 # 將變數 55 賦值給 VAR
((VAR = VAR + 1)) # 變數 VAR 加 1。注意這裡沒有 '$'
((++VAR)) # 另一種方法给 VAR 加 1。使用 C 語言風格的前缀自增
((VAR++)) # 另一種方法给 VAR 加 1。使用 C 語言風格的後缀自增
echo $((VAR * 22)) # VAR 乘以 22 並將结果送入命令
echo $[VAR * 22] # 同上,但為過時用法
bash 擁有傳統 Bourne shell 缺乏的 I/O 重新導向語法。bash 可以同時重新導向標準輸出和標準錯誤。
command &> file
command <<< "string to be read as standard input"
bash 3.0 支援行程內的正規表示式
[[ string =~ regex ]]
$'string' 形式的字串會被特殊處理。字串會被展開成 string,並像 C 語言那樣將反斜槓及緊跟的字元進行替換。
Bash 4.0 開始支援關聯陣列,通過類似AWK的方式,對於多維陣列提供了偽支援。
$ declare -A a # 宣告一個名為 a 的二維陣列
$ i=1; j=2
$ a[$i,$j]=5 # 將 Index 為 "$i,$j" 的位置賦值為 5
$ echo ${a[$i,$j]}
呼叫 Bash 時指定
--posix
或者在指令碼中聲明set -o posix
,可以使得 Bash 幾乎遵循 POSIX 1003.2 標準。若要保證一個 Bash 指令碼的移植性,至少需要考慮到 Bourne shell,即 Bash 取代的 shell。
./msh
msh> ls | wc -l
msh> pwd
msh> exit
# 回到執行前的 Shell