我是 Shell 脚本新手。我希望编写一个 Unix 脚本来调用 C 程序N= 2^{i}
,i= 1,2 ....20
;然后将此数据记录在文件中。
(该程序使用 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 脚本将会非常有帮助。
答案1
您不应该提示用户并从标准输入读取参数,而应该接受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;
}
告诉setlocale(LC_ALL, "");
C 库检查当前环境,并设置本地化以匹配。该程序仅使用该类LC_CTYPE
来确定哪些字符是空格(空格或制表符)。尽管如此,这是一个很好的做法:如果在某些时候您希望支持像ä
和 之类的字符€
,您可以切换到宽字符和 I/O。
作为学习者,您可以省略parse_long()
和parse_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 会忽略以 开头的行#
,因此您可以在文件开头使用此类注释或标题来说明每一列的用途。请参阅文档了解更多信息。我个人更喜欢以SVG
或PDF
格式保存绘图,这样它们就是小文件,但具有高质量的矢量图形。这是我特别推荐的课程作业。
答案2
您正在编译和链接,因此输出将是可执行文件,而不是目标文件,这使得 .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
.