編寫 Unix shell 腳本來呼叫 C 函式並將數據重定向到 .txt 文件
我是 Shell 腳本的新手。我想寫一個 Unix 腳本,它會為
N= 2^{i}
,i= 1,2 ....20
;呼叫一個 C 程序。然後將這些數據記錄在一個文件中。(該程序使用 C 中的梯形規則計算定積分,並返回每個項 N 的迭代結果和誤差。)
當我剛開始學習 C 時,我為梯形規則編寫了程式碼:
#include<stdio.h> #include<math.h> #define PI 3.14159265358979323846 float fn(float x) { float integrand; integrand = (1.0/(1.0+x*x)); return integrand; } int main() { int i,N; float a,b,sum=0,result=0,h; float error; printf("Enter the no of equally spaced points ="); scanf("%d",&N); printf("Enter the lower limit="); scanf("%f",&a); printf("Enter the upper limit="); scanf("%f",&b); h=(b-a)/(N-1); for(i=1;i<=N;i++) { sum=sum+fn(a+i*h); result=(fn(a)+fn(b)+2*sum)*h/2; error = fabs((atan(b)-atan(a))-result); //error = PI/2.0 - result; printf("N=%d result=%f error=%f\n", i, result, error); } printf("final result =%f\n", result); printf("cumulative error =%f\n", error); }
我執行此程式碼
gcc -o err.o trap_error.c -lm
我的 gcc 版本是
gcc (Ubuntu 5.4.0-6ubuntu1~16.04.2) 5.4.0 20160609
我在網際網路上隨機搜尋,但沒有找到有用的資訊,我想我也必須修改我的程式碼。如果您只是幫我編寫 Unix 腳本,然後將輸出重定向到
.txt
文件中。帶有解釋的 Unix 腳本將非常有幫助。
您正在編譯和連結,因此輸出將是執行檔,而不是目標文件,這使得 .o 後綴具有誤導性。
gcc -o err trap_error.c -lm
可能是一個更好的主意。
不清楚您要問什麼,但看起來您正在嘗試為其提供一些自動生成的輸入並將所有輸出重定向到文件。您可以使用以下命令
N= 2^{i}, i= 1,2 ....20;
在 bash 中生成:for ((i=2;i<2**20;i*=2)); do echo "$i" ; done
如果 -1 和 1 分別是你的下限和上限,那麼你可以在 $i 之後添加它們,並將每個這樣的三元組通過管道傳遞給程序的呼叫:
for ((i=2;i<2**20;i*=2)); do echo "$i -1 1" | ./err ; done > out.txt
該
> out.txt
部分會將所有 ./err 呼叫的所有輸出重定向到out.txt
.
與其提示使用者並從標準輸入中讀取參數,不如擁抱Unix 哲學,並改用命令行參數。
下面的例子有點長,因為我想展示我喜歡的參數檢查函式。
scanf()
函式族不檢查溢出,因此strto*()
需要使用。此外,數字後面可能偶爾會有垃圾(比如,‘12l’ - 最後是字母 L - 而不是 ‘121’),我個人想抓住它。#include <stdlib.h> #include <locale.h> #include <ctype.h> #include <stdio.h> #include <errno.h> /* Helper function to parse a double. * Returns 0 if successful, nonzero otherwise. */ static int parse_double(const char *s, double *v) { const char *end; double val; if (!s) return errno = EINVAL; end = s; errno = 0; val = strtod(s, (char **)&end); if (errno) return errno; if (!end || end == s) return errno = EINVAL; while (*end != '\0' && isspace(*end)) end++; if (*end != '\0') return errno = EINVAL; if (v) *v = val; return 0; } /* Helper function to parse a long. * Returns 0 if successful, nonzero otherwise. */ static int parse_long(const char *s, long *v) { const char *end; long val; if (!s) return errno = EINVAL; end = s; errno = 0; val = strtol(s, (char **)&end, 0); if (errno) return errno; if (!end || end == s) return errno = EINVAL; while (*end != '\0' && isspace(*end)) end++; if (*end != '\0') return errno = EINVAL; if (v) *v = val; return 0; }
其中,
parse_long()
支持十進制 (987
)、十六進制 (0x3DB
) 和八進制 (01733
) 表示法。然後
main()
就像int main(int argc, char *argv[]) { double min, max; long n; setlocale(LC_ALL, ""); /* Require "command N min max" -- four parameters, * including the executable file name (argv[0]). */ if (argc != 4 || !strcmp(argv[1], "-h") || !strcmp(argv[1], "--help")) { fprintf(stderr, "Usage: %s [ -h | --help ]\n", argv[0]); fprintf(stderr, " %s N min max\n", argv[0]); return EXIT_FAILURE; } if (parse_long(argv[1], &n) || n < 1L) { fprintf(stderr, "%s: Invalid N.\n", argv[1]); return EXIT_FAILURE; } if (parse_double(argv[2], &min)) { fprintf(stderr, "%s: Invalid minimum.\n", argv[2]); return EXIT_FAILURE; } if (parse_double(argv[3], &max)) { fprintf(stderr, "%s: Invalid maximum.\n", argv[3]); return EXIT_FAILURE; } if (min > max) { const double tmp = min; min = max; max = tmp; } /* ... */ return EXIT_SUCCESS; }
告訴 C庫
setlocale(LC_ALL, "");
檢查目前環境,並設置本地化以匹配。該程序僅使用LC_CTYPE
該類來確定哪些字元是空格(空格或製表符)。儘管如此,這是一個很好的實踐:如果在某些時候,您希望支持像ä
and這樣的字元€
,您可以切換到寬字元和 I/O。作為學習者,您可以省略
parse_long()
andparse_double()
,並在if
子句中替換為sscanf()
,而忽略本地化。它為您節省了幾行,#include <stdlib.h> #include <stdio.h> int main(int argc, char *argv[]) { double min, max; long n; /* Require "command N min max" -- four parameters, * including the executable file name (argv[0]). */ if (argc != 4 || !strcmp(argv[1], "-h") || !strcmp(argv[1], "--help")) { fprintf(stderr, "Usage: %s [ -h | --help ]\n", argv[0]); fprintf(stderr, " %s N min max\n", argv[0]); return EXIT_FAILURE; } if (sscanf(argv[1], " %ld", &n) != 1 || n < 1L) { fprintf(stderr, "%s: Invalid N.\n", argv[1]); return EXIT_FAILURE; } if (sscanf(argv[2], " %lf", &min) != 1) { fprintf(stderr, "%s: Invalid minimum.\n", argv[2]); return EXIT_FAILURE; } if (sscanf(argv[3], " %lf", &max) != 1) { fprintf(stderr, "%s: Invalid maximum.\n", argv[3]); return EXIT_FAILURE; } if (min > max) { const double tmp = min; min = max; max = tmp; } /* ... */ return EXIT_SUCCESS; }
但是在我看來,為什麼要學習一種在實踐中還不夠的方法呢?我個人知道一些情況下,諸如*“人名僅包含字母 A 到 Z”之*類的愚蠢假設需要花費數十個小時才能解決(計算集群上的 411 服務,使用者具有非英文名稱)。我們生活在一個全球化的世界裡,你們這些說英語的人最好已經排好隊,放棄你們愚蠢的假設。
人們似乎也無法在事後學習本地化。我遇到的大多數“有經驗的 C 程序員”似乎根本不知道也不關心本地化或字元集問題。(嗯,除了在所有地方使用 UTF-8之外。)這意味著其他人必須花費數小時來解決他們的錯誤假設,浪費時間和精力……可恥。
當您的程序採用可以接受命令行參數的形式時,您可以使用 Bash 循環,例如
for (( i=1; i<=20; i++ )); do ./yourprog $i 0.0 10.0 ; done > output.txt
請注意,如果您將數據輸出到空格或製表符分隔的列中,
*
或者-
如果某列缺少數據,您可以使用gnuplot
來繪製數據。例如,如果你
output.txt
有#N result error 1 3.1 0.04159265359 2 3.14 0.00159265359 3 3.141 0.00059265359
依此類推,您可以使案例如查看數據
gnuplot -p -e 'plot "output.txt" u 2:3 notitle w lines'
Gnuplot 會忽略以 a 開頭的行
#
,因此您可以將其用於文件開頭的註釋或標題,以說明每列的用途。有關詳細資訊,請參閱文件。我個人更喜歡以格式保存繪圖SVG
,