无忧启动论坛

 找回密码
 注册
搜索
系统gho:最纯净好用系统下载站投放广告、加入VIP会员,请联系 微信:wuyouceo
楼主: 不点
打印 上一主题 下一主题

cish 开发构想

    [复制链接]
121#
发表于 2018-5-5 12:45:01 | 只看该作者
不点 发表于 2018-5-5 08:30
发现我俩有许多共同看法,缘分。

在 2000 年以前,我就有开发输入法的打算,也开始收集汉字和词库了, ...

https://github.com/rime/weasel
输入法。

点评

这个输入法很不错,以前经人介绍,我也试验过。但我受 “紫光拼音” 之类的影响大,所以我在 Windows 下使用华宇拼音(从 “紫光拼音” 改名得来)。而在 Linux 下一直使用系统默认的输入法,并不追求输入法的技术指  详情 回复 发表于 2018-5-5 16:53
回复

使用道具 举报

122#
 楼主| 发表于 2018-5-5 16:53:03 | 只看该作者
freesoft00 发表于 2018-5-5 12:45
https://github.com/rime/weasel
输入法。

这个输入法很不错,以前经人介绍,我也试验过。但我受 “紫光拼音” 之类的影响大,所以我在 Windows 下使用华宇拼音(从 “紫光拼音” 改名得来)。而在 Linux 下一直使用系统默认的输入法,并不追求输入法的技术指标(其实我也不了解究竟有哪些指标)。
回复

使用道具 举报

123#
 楼主| 发表于 2018-5-6 13:56:07 | 只看该作者
学习 “管道、重定向” 知识的时候,遇到一个麻烦(看起来像是 C 库函数的一个 bug),贴出来供大家讨论。

  1. /* DUP.C: This program uses the variable old to save the original stdout. It then opens a new file named new and forces stdout to refer to it. Finally, it restores stdout to its original state. */

  2. #include <stdlib.h>
  3. #include <stdio.h>
  4. #include <unistd.h>

  5. int main( void )
  6. {
  7.    int old;
  8.    FILE *New;
  9.    old = dup( 1 );   /* "old" now refers to "stdout" */
  10.    if( old == -1 ) {
  11.       perror( "dup( 1 ) failure" );
  12.       exit( 1 );
  13.    }

  14.    printf ("aaaaa ");   <----- 这里必须打印点东西,后面才正常。至少要打印一个字符。

  15.    if ( ( New = fopen( "data", "w" ) ) == NULL ) {
  16.       puts( "Can't open file 'data'\n" );
  17.       exit( 1 );
  18.    }

  19.    fflush( stdout );

  20.    /* stdout now refers to file "data" */
  21.    if ( -1 == dup2( fileno( New ), 1 ) ) {
  22.       perror( "Can't dup2 stdout" );
  23.       exit( 1 );
  24.    }
  25.    puts ( "This goes to file 'data'\n" );
  26.    /* Flush stdout stream buffer so it goes to correct file */
  27.    fflush ( stdout );
  28.    fclose ( New );
  29.    /* Restore original stdout */
  30.    dup2 ( old, 1 );
  31.    puts ( "This goes to stdout\n" );
  32.    puts ( "The file 'data' contains:\n" );
  33.    system ( "cat data" );
  34.    return 0;
  35. }
复制代码


用 gcc  dup.c 或 g++  dup.c 来编译,生成 a.out 文件。然后执行  ./a.out 即可测试。

上述代码输出结果是正确的:

  1. aaaaa This goes to stdout   <------ 这输出到终端,正确。

  2. The file 'data' contains:

  3. This goes to file 'data'     <------ data 文件的内容是正确的
复制代码

如果把   printf ("aaaaa ");   注释掉,或者改成    printf (""); 即,打印 0 个字符,则后面想要写入 data 文件的内容,都仍旧写到终端了,而不是正确写入到 data 文件中。

这里贴出错误的输出结果,供大家研究:

  1. This goes to file 'data'   <------ 这一句本应在 data 文件中,却打印到终端了!

  2. This goes to stdout

  3. The file 'data' contains:     <------ 下一行开始显示 data 文件的内容,但 data 的内容是空的!
复制代码

回复

使用道具 举报

124#
 楼主| 发表于 2018-5-7 00:54:38 | 只看该作者
试着实现了 “标准输出重定向”。注意,目前只实现了标准输出(“>” 或 “1>”),没有实现标准输入和标准错误。而且只能重定向到文件,不能重定向 fd (即,尚不支持 2>&1 之类的功能)。

附加要求: “>” 或 “1>” 两边都必须有空格或 Tab 来分隔(没有分隔时,会出错)。

举例:

ls  >  /dev/null
pwd  >  my_tmp_out_file

回复

使用道具 举报

125#
发表于 2018-5-7 21:27:05 | 只看该作者
不点 发表于 2018-5-5 08:30
发现我俩有许多共同看法,缘分。

在 2000 年以前,我就有开发输入法的打算,也开始收集汉字和词库了, ...

嗯,确实有不少gnux系统看法,不点大师的见解与屮有共同点。
输入法屮目前正在学习,不过,屮氏拼形派,因为输入简洁无重才适合屮的需要。
自已想搞输入法的目的,首要氏支持自用的码案,其次才氏支持拼形“万码奔腾”的想法。
回复

使用道具 举报

126#
发表于 2018-5-7 21:35:22 | 只看该作者
屮以前见有gnux程序员说gnu的C标准库的IO函数实现没有glib的函数封装的好,有时会出问题。
所以建议尽可能使用glib的函数,而不氏使用标准C的库函数,也不知这个说法氏否确实。


回复

使用道具 举报

127#
 楼主| 发表于 2018-5-8 09:18:36 | 只看该作者
本帖最后由 不点 于 2018-5-8 09:25 编辑

搜到 How do I combine stdout and stderr for different shells?

这是题主的提问:

I know the answer for some shells, e.g. with cmd.exe you can:

some.exe > out.txt 2>&1

How do you achieve the same for other shells (bash, ksh, tcsh, powershell, etc)?

以下是回答:

Fish shell

To redirect both standard output and standard error to the file all_output.txt, you can write:

echo Hello > all_output.txt ^&1

补充回答 1:if piping output to next command, use 2>| instead: echo Hello 2>| less

补充回答 2:The piping syntax may have changed. My fish shell uses the same syntax "^&1" for both.

补充回答 3:2>&1 also works — ^ is essentially fish's shorthand for >2. Note that 2>| less or ^| less by itself pipes only stderr; to pipe both out and err, do ^&1 | less

补充回答 4:Syntax shorthands for redirecting/piping both out and err are still under discussion github.com/fish-shell/fish-shell/issues/110


又一个回答:

For csh and tcsh

some.exec >& out.txt

补充回答:for tcsh, you also have some.exec |& less





我按照前面 “补充回答4” 的指示,前往 github.com/fish-shell/fish-shell/issues/110 瞧了瞧,那里讨论得非常混乱、恐怖,我都不敢读完。

我领会到,表面上平静的 shell 背后,还真是 under discussion ——仍然在 “暗流涌动” 或 “波涛翻滚”。
回复

使用道具 举报

128#
 楼主| 发表于 2018-5-9 09:13:11 | 只看该作者
IO Redirection - Swapping stdout and stderr

其中提到这样的用法:

ls    3>&2    2>&1    1>&3    3>&-






这几天学习 “重定向”,有点眉目了。C 库函数(或系统调用)对文件描述符的操作,有以下这几种:

1、创建文件描述符,用 open 函数。
2、复制文件描述符,用 dup 或 dup2 函数。
3、关闭文件描述符,用 close 函数。

shell 的表示法,不那么直观,容易看花了眼,不容易理解。其实,3>&2 是把当前 2 的 “值” 赋给 3。如果写成 3=2 (或 fd3=fd2)就容易理解了。

上述 3>&2  2>&1  1>&3  3>&- 的作用,其实相当于 swap (1, 2) 或 swap (fd1, fd2)。文件描述符 3 只是临时用用罢了。

如果把 3,2,1 理解为 “变量”(比如说 fd3,fd2,fd1),再把

3>&2     2>&1     1>&3     3>&-

写成

3=2  <--- 创建临时 “变量” 3,并把 “变量” 2 备份到临时 “变量” 3
2=1  <--- 把 “变量” 1 的值赋给 “变量” 2
1=3  <--- 把临时 “变量” 3 的值,也即原来旧的 “变量” 2 的值赋给 “变量” 1。以上三步完成了 “变量” 1 和 “变量” 2 的交换。
free(3) <--- 最后一步,释放掉临时 “变量” 3

或者把 3,2,1 理解为 “文件”,写成

copy   2   3   <------ 把 “文件” 2 复制为临时 “文件” 3
copy   1   2   <------ 把 “文件” 1 复制为 “文件” 2
copy   3   1   <------ 把临时 “文件” 3 复制为 “文件” 1
rm  3     <-------- 删除临时 “文件” 3

这样就明白了。


回复

使用道具 举报

129#
 楼主| 发表于 2018-5-9 10:17:44 | 只看该作者
震惊不?

刚才在 bash 下试了试,果然

3>&2     2>&1     1>&3     3>&-



3<&2     2<&1     1<&3     3<&-

完全等价。 而且,其中的 ">" 和 "<" 可以任意换用。

其实我是先猜测,再验证。验证的结果,证实了猜测是正确的。

#include <unistd.h>
int main( void )
{
   write( 1, "This goes to stdout\n", 21 );
   write( 2, "This goes to stderr\n", 21 );
   return 0;
}

这个文件叫做 std.c,用 gcc  std.c 或 g++  std.c 来编译,生成 a.out 文件。在 bash 之下进行测试,以下是测试结果:

user@ttyd:~$ (./a.out 3>&2 2>&1 1>&3 3>&-) > /dev/null
This goes to stdout
user@ttyd:~$ (./a.out 3>&2 2>&1 1>&3 3>&-) 2> /dev/null
This goes to stderr
user@ttyd:~$ (./a.out 3>&2 2>&1 1>&3 3<&-) 2> /dev/null
This goes to stderr
user@ttyd:~$ (./a.out 3>&2 2>&1 1>&3 3<&-) > /dev/null
This goes to stdout
user@ttyd:~$ (./a.out 3<&2 2>&1 1>&3 3<&-) > /dev/null
This goes to stdout
user@ttyd:~$ (./a.out 3<&2 2>&1 1>&3 3<&-) 2> /dev/null
This goes to stderr
user@ttyd:~$ (./a.out 3<&2 2<&1 1>&3 3<&-) 2> /dev/null
This goes to stderr
user@ttyd:~$ (./a.out 3<&2 2<&1 1>&3 3<&-) > /dev/null
This goes to stdout
user@ttyd:~$ (./a.out 3<&2 2<&1 1<&3 3<&-) > /dev/null
This goes to stdout
user@ttyd:~$ (./a.out 3<&2 2<&1 1<&3 3<&-) 2> /dev/null
This goes to stderr

可见,">" 与 "<" 任意互换,都能工作。

这暗示,其实 ">" 与 "<" 的区分,不是根本性的;而具有根本性的,仅仅是代表 “文件描述符” 的数字而已。

就是说,无论 3>&2 还是 3<&2,其含义都是一样的,即 3=2(把 2 的 “值” 赋给 3)。



回复

使用道具 举报

130#
发表于 2018-5-9 13:08:10 来自手机 | 只看该作者
本帖最后由 slore 于 2018-5-9 13:14 编辑

使用=符号更好,不苟同。
shell中还有数值运算,2=3的话,
=号有多重意思,且和常识(二等于三)不相符。
采用符号>表达“定向”之意也很直观。
用STDOUT的话,最直观,不过字数多,就直接采取数字和代码保持一致,用了&地址符也受到C语言的影响吧。

点评

安卓系统下的火狐有 bug,有很大概率会产生乱码。我猜你可能是在用火狐。  详情 回复 发表于 2018-5-9 13:16
手机汉字发送用问题?  发表于 2018-5-9 13:09
回复

使用道具 举报

131#
 楼主| 发表于 2018-5-9 13:16:12 | 只看该作者
slore 发表于 2018-5-9 13:08
使用=符号更好,不苟同。
shell中还有数值运算,2=3的话,
=号有多重意思,且和常识(二等于三)不相符。 ...


安卓系统下的火狐有 bug,有很大概率会产生乱码。我猜你可能是在用火狐。
回复

使用道具 举报

132#
发表于 2018-5-9 13:19:53 | 只看该作者
不点 发表于 2018-5-9 13:16
安卓系统下的火狐有 bug,有很大概率会产生乱码。我猜你可能是在用火狐。

自带华为浏览器,直接帖子下面的回复有问题,点回复按钮,或者编辑就可以了。
回复

使用道具 举报

133#
 楼主| 发表于 2018-5-11 00:07:30 | 只看该作者
通报一下,最近学习了 pipe 等知识,感觉这些知识已经够用了,可以编写一个类似于 bash 的 shell 了。

但是,我打算慢慢来,不急于去写一个与 bash 一样的 shell。

利用 Linux 强大的 clone () 系统调用,用线程的方式,或许可以实现一个功能更丰富、更完善的 shell。

回复

使用道具 举报

134#
 楼主| 发表于 2018-5-11 11:01:47 | 只看该作者
Linux 中 fork,vfork 和 clone 详解(区别与联系)
https://blog.csdn.net/gatieme/article/details/51417488

英文有很多文章(帖子)讲解 clone 的精髓,但中文就很少有了。上述这篇文章很棒,深入浅出,是入门的好材料。
回复

使用道具 举报

135#
 楼主| 发表于 2018-5-11 17:37:55 | 只看该作者
测试程序,展示一下 clone() 的灵活(灵活=强大)。clone 出来的子进程与父进程共享内存。父进程给变量 a 赋值:a = 1;子进程让变量 a 递增:++a;子进程打印出 a = 2。父进程也打印出 a = 2。这说明子进程更改了父进程中变量 a 的值——就是说,父子进程共用了同一个内存空间。注意 fork() 不可能做到这一点。

另外,父子进程之间所建立的管道,工作也正常。父进程把命令行参数作为字符串输出到管道。子进程从管道接收字符,并输出到控制台。

  1. /* pipe.c */

  2. #define _GNU_SOURCE
  3. #include <malloc.h>
  4. #include <signal.h>
  5. #include <sys/types.h>
  6. #include <syscall.h>
  7. #include <sched.h>
  8. #include <sys/wait.h>
  9. #include <sys/utsname.h>
  10. #include <sched.h>
  11. #include <string.h>
  12. #include <stdio.h>
  13. #include <stdlib.h>
  14. #include <unistd.h>

  15. int a;
  16. char buf;
  17. int pipefd[2];
  18. char stack[8192];

  19. int clone_func()
  20. {
  21.         printf ("This is child, pid = %d, ppid = %d, a = %d\n", getpid(), getppid(), ++a);
  22.         /* Child reads from pipe */
  23.         close(pipefd[1]);          /* Close unused write end */
  24.         while (read(pipefd[0], &buf, 1) > 0)
  25.                 write(STDOUT_FILENO, &buf, 1);
  26.         write(STDOUT_FILENO, "\n", 1);
  27.         close(pipefd[0]);
  28.         exit(EXIT_SUCCESS);
  29. }
  30. int
  31. main(int argc, char *argv[])
  32. {
  33.     if (argc != 2) {
  34.         fprintf(stderr, "Usage: %s <string>\n", argv[0]);
  35.         exit(EXIT_FAILURE);
  36.     }
  37.     if (pipe(pipefd) == -1) {
  38.         perror("pipe");
  39.         exit(EXIT_FAILURE);
  40.     }
  41.     a = 1;
  42.     printf ("Creating child ...\n");
  43.     clone (&clone_func, stack + 8192, CLONE_VM, 0);
  44.        /* Parent writes argv[1] to pipe */
  45.         close(pipefd[0]);          /* Close unused read end */
  46.         write(pipefd[1], argv[1], strlen(argv[1]));
  47.         close(pipefd[1]);          /* Reader will see EOF */
  48.         //wait(NULL);                /* Wait for child */
  49.     sleep (5);
  50.     printf ("This is parent, pid = %d, ppid = %d, a = %d\n", getpid(), getppid(), a);
  51.     return 0;
  52. }
复制代码


编译和运行结果如下:

user@ttyd:~$ gcc pipe.c
user@ttyd:~$ ./a.out ttttttttttttt
Creating child ...
This is child, pid = 1802, ppid = 1801, a = 2
ttttttttttttt
This is parent, pid = 1801, ppid = 1690, a = 2
user@ttyd:~$
回复

使用道具 举报

136#
 楼主| 发表于 2018-5-14 08:20:03 | 只看该作者
Linus 本人关于 “线程、进程” 的阐述:

http://www.evanjones.ca/software/threading-linus-msg.html

他提到了用 clone () 可以支持实现一个外部命令 cd。我这就来验证一下。

如下这段小程序用 gcc 编译,可生成一个叫做 cdsrc 的外部命令(把生成的 a.out 更名为 cdsrc)。

  1. /* cdsrc.c */
  2. #include <unistd.h>
  3. int main()
  4. {
  5.     chdir ("src");
  6.     return 0;
  7. }
复制代码


注意上述 cdsrc 命令在 bash 下肯定不会得到理想的结果,因为外部命令不可能改变父进程的当前目录(因为 bash 不使用 clone 系统调用)。

如下这个程序是用来测试 clone 的强大功能的。用 gcc 编译,生成 a.out,它会调用外部的 cdsrc 命令。


  1. /* cd.c */
  2. #define _GNU_SOURCE
  3. #include <malloc.h>
  4. #include <signal.h>
  5. #include <sys/types.h>
  6. #include <syscall.h>
  7. #include <sched.h>
  8. #include <sys/wait.h>
  9. #include <sys/utsname.h>
  10. #include <sched.h>
  11. #include <string.h>
  12. #include <stdio.h>
  13. #include <stdlib.h>
  14. #include <unistd.h>
  15. char stack[8192];
  16.          
  17. int child_func()
  18. {        
  19.     execl ("./cdsrc", "./cdsrc", NULL);    //chdir ("src");
  20.     return 0;
  21. }            
  22.       
  23. int main()
  24. {
  25.     char * pwd;
  26.     clone (&child_func, stack + 8192, CLONE_VM | CLONE_FS, 0);
  27.     wait(NULL);
  28.     sleep (5);
  29.     pwd = getcwd(0, 0);
  30.     printf ("pwd=%s\n", pwd);
  31.     free (pwd);
  32.     return 0;//exit (1);
  33. }
复制代码


运行结果是正确的,就是说,父进程的当前目录被外部命令成功修改了:

user@ttyd:~$ ./a.out
pwd=/home/user/src
user@ttyd:~$

回复

使用道具 举报

137#
 楼主| 发表于 2018-5-15 02:08:45 | 只看该作者
初步完成了对 cish 的改造:对 fork() 的调用,改造为调用 clone()。改造后的 cish 可以运行(测试用的)外部命令 cdsrc 从而进入 src 目录。以下是测试结果:

user@ttyd:~$ cish
[cling]$ pwd
/home/user
[cling]% ./cdsrc
[cling]% pwd
/home/user/src
[cling]% ls
bindings/  CMakeLists.txt   configure*   docs/      include/  LICENSE.TXT    llvm.spec.in  README.txt           resources/  test/   unittests/
cmake/     CODE_OWNERS.TXT  CREDITS.TXT  examples/  lib/      LLVMBuild.txt  projects/     RELEASE_TESTERS.TXT  runtimes/   tools/  utils/
[cling]%
回复

使用道具 举报

138#
 楼主| 发表于 2018-5-21 11:11:36 | 只看该作者
本帖最后由 不点 于 2018-5-21 11:54 编辑

今天试验了一下,在 cish 下执行 echo $(ls 2>/dev/null) 竟然成功,也就是说,$() 【以及反引号 ``】 里面竟然支持重定向!

也就是说,wordexp 函数其实已经内建重定向支持!

干脆再试试————

echo $(echo ttt | ls)

echo $(echo ttt | ls | echo yyy)

以上两条命令都能够得到想要的结果!

这说明 wordexp 函数已经内建管道支持!

谁来帮忙挖掘一下,看看如何利用 wordexp 里面的代码,来让 wordexp 能够直接处理管道和重定向?

提醒一下,wordexp 前面曾有提及,它是 C 库函数。





又试验一次,用 type set 来试验,这次就基本知道是怎么回事了。$() 实际上应该是调用了 /bin/sh 来运行命令【就是 system() 函数】。

[cling]% echo $(type set)
set is a special shell builtin
[cling]%



回复

使用道具 举报

139#
 楼主| 发表于 2018-5-24 08:29:48 | 只看该作者
前些日子借用 cling 里面内置的 setStdStream 函数,写了一个 redir 函数(前面已经介绍过了)。不过,那只能对输出进行重定向,不能对输入进行重定向。

这些天我在考虑,如何设计一个使用方便的、比较完善的重定向函数。

redir 函数就不错,只是不完善而已。注意 redir 在当前进程就开始起作用了。这不同于 shell 命令行的 “>” 之类的重定向(只在子进程起作用)。

设计好重定向函数,让它能对输入和输出进行,也要让它能够设置重定向文件的读写位置。就是说,让它灵活、方便、好用。
回复

使用道具 举报

140#
 楼主| 发表于 2018-5-30 09:30:23 | 只看该作者
感谢 2011hubeilcsun 在一楼的评论中提供 RT-Thread —— 中国自主的开源物联网操作系统:

https://www.rt-thread.org/index.html

它的 finsh 命令处理似乎很强大,同时具有 C 解释和 shell 解释功能。

希望我们当中有人前去摸索一下,给出更详细的信息。

回复

使用道具 举报

141#
 楼主| 发表于 2018-5-31 05:40:54 | 只看该作者
这些天通过反复思考(权衡),设计并实现了一个叫做 fin() 的函数。

f 的含义是 file
in 的含义是 stdin

fin 的含义是 “指定标准输入文件”。

用法:

int fin (char *file); 作用:设定 file 为后续命令的标准输入文件(即,标准输入重定向)。

int fin (); 【注意:参数表为空,即,不带参数】作用:解除先前对标准输入的重定向,回到重定向之前的状态。

【还有其它用法,比如 int fin(int fd, int flags); 等,暂不详细介绍】。

举例:

[cling]% fin("tt")   <----- 假定 tt 是当前目录下的一个文本文件,它将成为后续语句的标准输入。
(int) 0, 0, 0x00000000, 0  <----- 返回值为 0,表示重定向的设定是成功的。如果文件 tt 不存在,则会失败。
[cling]% int ch; while (EOF != (ch = getchar())) putchar(ch); putchar('\n');
【此处会显示 tt 的内容,证明标准输入确实已经重定向到 tt 文件了】
[cling]% fin() <----- 这条语句关闭重定向,后续的标准输入就仍然是控制台。
(int) 0, 0, 0x00000000, 0 <----- 返回值为 0,表示关闭的动作是成功的。
[cling]% int ch; while (EOF != (ch = getchar())) putchar(ch); putchar('\n');
input_line_34:2:6: error: redefinition of 'ch'
int ch; while (EOF != (ch = getchar())) putchar(ch); putchar('\n');
     ^
input_line_32:2:6: note: previous definition is here
int ch; while (EOF != (ch = getchar())) putchar(ch); putchar('\n');
     ^
[cling]% while (EOF != (ch = getchar())) putchar(ch); putchar('\n');
tyrtytyt     <----- 这是键盘敲入的
tyrtytyt     <----- 这是回显出来的
iuyuuiy     <----- 这是键盘敲入的
iuyuuiy     <----- 这是回显出来的
<----- 此处按 Ctrl-D 就结束输入状态了。
这个测试证明了,标准输入确实已经恢复为控制台了。
[cling]% exit <----- 这条命令退出 cish
user@ttyd:~$ <----- 已经回到 bash 的提示符了
回复

使用道具 举报

142#
 楼主| 发表于 2018-5-31 11:32:49 | 只看该作者
简单复制 fin() 的代码,并加以修改,也就实现了 fout() 和 ferr() 函数。其用法也与前面介绍的 fin() 类似,不再重复介绍。

至此,适用于当前进程的重定向功能,可以说是完整实现了。


回复

使用道具 举报

143#
 楼主| 发表于 2018-6-1 17:30:09 | 只看该作者
本帖最后由 不点 于 2018-6-1 18:19 编辑

搜到话题 “把输出到控制台的信息重定向到字符串”

redirect output of an function printing to console to string
https://stackoverflow.com/questions/19485536/redirect-output-of-an-function-printing-to-console-to-string/19499003

其中有如下的代码(在 cish 下测试成功,就不贴测试结果了):


  1.     char buffer[1024];
  2.     auto fp = fmemopen(buffer, 1024, "w");
  3.     if ( !fp ) { std::printf("error"); return 0; }

  4.     auto old = stdout;       <-------- 先备份 stdout 的值到 old 变量中
  5.     stdout = fp;                <-------- 这一句是关键

  6.     foo(); //all the std::printf goes to buffer (using fp);

  7.     std::fclose(fp);
  8.     stdout = old; //reset    <-------- 恢复 stdout 为先前保存的值

  9.     std::printf("<redirected-output>\n%s</redirected-output>", buffer);
复制代码


就是说,通过临时给 stdout 赋值,可以实现重定向标准输出到内存变量 buffer 中。这个思路还真新颖!
回复

使用道具 举报

144#
 楼主| 发表于 2018-6-2 15:26:56 | 只看该作者
关于 “重定向到内存” 的问题

昨天找到的 fmemopen 函数,它有 file stream(即 FILE *,文件指针),却没有 file descriptor(即,fileno),还得修改 stdout 、stdin、stderr 等标准流指针的值才能用于 “重定向到内存”,这不太安全(容易带来副作用)。

今天又使劲搜,搜到了 mmap 和 memfd_create 两个系统调用,能够让内存文件拥有 fd(即 fileno)。后者是 Linux 特有的系统调用,用起来似乎更方便一些。

这样,用户可以用 memfd_create 函数创建一个内存文件,函数的返回值就是 fd,可以用前面介绍的 fin、fout、ferr 函数分别把标准输入、标准输出、标准错误重定向到 fd。至于说如何重定向到 fd,前文没有介绍,现在介绍:

用法:int fin (int fd);
作用:把 “标准输入” 重定向到先前已经打开了的文件(fd 是其文件描述符,或者说是 fileno)。

fout 和 ferr 也有同样的用法,就不重复介绍了。

回复

使用道具 举报

145#
 楼主| 发表于 2018-6-2 16:54:28 | 只看该作者
理解 “here document”

bash 有 here document 的概念。here document 能充当标准输入 “文件”,确实很方便。

新版 bash 还增加了 here string 的用法。

无论 here document 还是 here string,本质上都是 “字符串”,都可以看成 “无名字符串变量”。

因此,bash 本质上是能够把标准输入重定向为 “无名字符串变量”。

利用 Linux 的 memfd_create 函数创建内存文件,并用于重定向标准输入,可以完成同样的任务。内存文件当然也可以用于重定向标准输出和标准错误。

如果把同一个内存文件首先用作标准输出,即,保存前一条命令的输出结果;然后再用作后一条命令的标准输入,这样,甚至就实现了一种 “轻量级” 的 “管道” 的功能(一般适用于小文件,即,一次性读,一次性写)。因此,用 “内存文件” 进行重定向,在功能上更灵活强大、更丰富完善一些。


回复

使用道具 举报

146#
 楼主| 发表于 2018-6-7 18:57:48 | 只看该作者
搜 change controlling terminal,搜到如下解决办法
https://www.linuxquestions.org/questions/linux-software-2/how-to-you-get-a-new-controlling-terminal-779158/


ioctl(FUTURE_CONTROLLING_TTY, TIOCSCTTY, 0 /* 1 means steal if tty is in use */);
回复

使用道具 举报

147#
 楼主| 发表于 2018-6-10 12:37:57 | 只看该作者
本帖最后由 不点 于 2018-6-11 18:24 编辑

在完成了 IO 重定向的主要设计工作之后,这几天考虑了 pipe 的设计问题,权衡出如下方案。

这里所说的 “管道” 是针对 “函数” 进行操作的。那些准备用管道连接起来的各个函数,称为管道成员。

当管道真正开始工作的时候,这些成员会被放在各自的进程中。每个成员都有一个进程,它们都是当前进程 cish 的子进程,但它们都与 cish 共享虚拟内存。可以想象为,它们有点类似 “线程” 的某些特征。嗯——让子进程与父进程共享虚拟内存——这是没问题的,因为 Linux 的 clone 函数很强大,可以做到这一点(前面已经介绍过 clone 函数了)。我注意到,当子进程执行 exec 系统调用时,它会自动从共享的虚拟内存分离出去【就像本来所用的不是 clone() 而是 fork() 那样】,不再与父进程 cish 共享虚拟内存。然而如果子进程 func 还没有开始执行 exec 系统调用,则它一直与 cish 共享虚拟内存,也就是说,它可以修改 cish 的变量(我想,这应该包括可以修改 cish 的环境变量)。

初始时,管道成员个数为 0。

用一个 pad(func) 函数来增加一个管道成员。func 是一个函数 void func(void)。pad 的意思是 Pipe ADd。

当结束时,最后一个函数用 pen(func) 来添加。pen 的意思是 Pipe ENd。

pen(func) 会真正开始让 pipe 生效(即,运行 pipe);在运行之前,它会顺便把管道成员个数清零,以便下次使用 pad (func) 能够开启另一条管道,而不是继续加长现有管道。

既然各个管道成员是 C 语言函数,那当然能够运行 shell 命令,因为 shell 命令可以包装在 C 函数中。目前我们执行 shell 命令 mycmd 就相当于执行 C 语言函数 shellInterpreter("mycmd");

利用 C++11 的 lambda 函数,用户自己可以将一个程序代码片段包装成一个无名函数,作为参数,传递给 pad() 和 pen(),这样就相当于,让管道能够支持连接不同的代码块。

以上仅仅是设计思路,还没有开始做。

回复

使用道具 举报

148#
 楼主| 发表于 2018-6-11 18:17:36 | 只看该作者
好不容易啊!排错排得很辛苦。不过总算成功了。在 cish 下执行:

pad([]{shellInterpreter("ls");})
pen([]{shellInterpreter("cat");})

两条命令,成功将 ls 的标准输出(通过管道)重定向为 cat 的标准输入。

记录一下所遇到的困难,以及如何克服的。困难在 Linux 的 clone() 系统调用。我猜这个系统调用有 bug,导致我反复折腾都是失败。最后,绕过那个坑,拐个弯,做成了。

我用 clone 把每个管道成员包装到子进程里面。然而,把这些子进程设置成同一个进程组(setpgid 函数)总是失败。后来,我先用 clone 把当前 cish 进程克隆出去,建立一个进程组(这是多 clone 了一次,也算是一个代价吧,系统负担肯定有所增加)。然后又从新进程中再 clone 那些管道成员(它们自然属于新的进程组,无需调用 setpgid 函数来修改它们的进程组),而且让 clone 出的进程仍然是 cish 的子进程(用 CLONE_PARENT 来实现)。折腾了周末的两天,终于调试通过了。其它一些杂七杂八的困难也有,不过,也不算太难,就不说了。

下面展示一下测试结果:


  1. user@ttyd:~$ cish
  2. [cling]$ pad([]{shellInterpreter("ls");})
  3. (int) 0, 0, 0x00000000, 0
  4. [cling]%
  5. [cling]%
  6. [cling]% pen([]{shellInterpreter("cat");})
  7. antiattack.sh
  8. a.out
  9. build
  10. build_deb_script
  11. ccsh.tar.gz
  12. clone.c
  13. clone.sh
  14. data
  15. dup.c
  16. flush.c
  17. id.sh
  18. inst
  19. Makefile
  20. nohup.out
  21. obj
  22. pid.c
  23. pipe
  24. pipe.c
  25. read.sh
  26. src
  27. src.diff
  28. src.diff.gz
  29. std.c
  30. tsh.c
  31. tt
  32. union.c
  33. variant.c
  34. (int) 32764, -4294934532, 0x00007ffc, 077774
  35. [cling]%
  36. [cling]% exit
  37. user@ttyd:~$
复制代码

回复

使用道具 举报

149#
 楼主| 发表于 2018-6-12 21:33:39 | 只看该作者
今天验证一下,管道成员可以修改父进程 cish 中的变量。注意,bash 的管道成员不可能修改父进程(即 bash 当前进程)中的变量。

在下面的测试代码中,首先在当前进程设定两个变量 myvar1=13,myvar2=57。然后启动管道,管道中的两个成员分别修改这两个变量的值为 24 和 68。管道执行完成之后,回到父进程(也就是 cish 当前所在的进程),立即验证这两个变量的值:它们确实是修改后的值了。


  1. user@ttyd:~$ cish
  2. [cling]$ int myvar1=13; int myvar2=57;
  3. [cling]% pad([]{myvar1=24;})
  4. (int) 0, 0, 0x00000000, 0
  5. [cling]% pen([]{myvar2=68;})
  6. (int) 0, 0, 0x00000000, 0
  7. [cling]% myvar1
  8. (int) 24, -4294967272, 0x00000018, 030
  9. [cling]% myvar2
  10. (int) 68, -4294967228, 0x00000044, 0104
  11. [cling]% exit
  12. user@ttyd:~$
复制代码


回复

使用道具 举报

150#
 楼主| 发表于 2018-6-13 06:07:10 | 只看该作者
当试图在管道成员中用 putenv() 或 setenv() 来增加一个新的环境变量的时候,cish 主进程崩溃了。

但是,如果在管道成员中用 putenv() 或 setenv() 来修改一个原先在 cish 中已经存在的环境变量,则 cish 不会崩溃,而且,环境变量成功被修改。

我猜之所以崩溃,是因为子进程试图扩大父进程的(可写)内存空间(有鉴于父子进程的内存空间是共用的)。



回复

使用道具 举报

您需要登录后才可以回帖 登录 | 注册

本版积分规则

小黑屋|手机版|Archiver|捐助支持|无忧启动 ( 闽ICP备05002490号-1 )

闽公网安备 35020302032614号

GMT+8, 2025-12-11 19:19

Powered by Discuz! X3.3

© 2001-2017 Comsenz Inc.

快速回复 返回顶部 返回列表