【问题标题】:Run C or C++ file as a script将 C 或 C++ 文件作为脚本运行
【发布时间】:2011-01-29 18:06:23
【问题描述】:

所以这可能是一个长镜头,但是有没有办法将 C 或 C++ 文件作为脚本运行?我试过了:

#!/usr/bin/gcc main.c -o main; ./main

int main(){ return 0; }

但它说:

./main.c:1:2: error: invalid preprocessing directive #!

【问题讨论】:

标签: c++ c shell


【解决方案1】:

简答:

//usr/bin/clang "$0" && exec ./a.out "$@"
int main(){
    return 0;
}

诀窍在于您的文本文件必须是有效的 C/C++ 代码和 shell 脚本。记得在解释器到达 C/C++ 代码之前从 shell 脚本中 exit,或者调用 exec 魔法。

使用chmod +x main.c; ./main.c 运行。

不需要像 #!/usr/bin/tcc -run 这样的 shebang,因为类 unix 系统已经在 shell 中执行文本文件。

(改编自this comment


我在我的 C++ 脚本中使用了它:

//usr/bin/clang++ -O3 -std=c++11 "$0" && ./a.out; exit
#include <iostream>
int main() {
    for (auto i: {1, 2, 3})
        std::cout << i << std::endl;
    return 0;
}

如果您的编译行增长过多,您可以使用预处理器(改编自 this answer),正如这段普通的旧 C 代码所示:

#if 0
    clang "$0" && ./a.out
    rm -f ./a.out
    exit
#endif
int main() {
    return 0;
}

当然你可以缓存可执行文件:

#if 0
    EXEC=${0%.*}
    test -x "$EXEC" || clang "$0" -o "$EXEC"
    exec "$EXEC"
#endif
int main() {
    return 0;
}

现在,对于真正古怪的 Java 开发人员:

/*/../bin/true
    CLASS_NAME=$(basename "${0%.*}")
    CLASS_PATH="$(dirname "$0")"
    javac "$0" && java -cp "${CLASS_PATH}" ${CLASS_NAME}
    rm -f "${CLASS_PATH}/${CLASS_NAME}.class"
    exit
*/
class Main {
    public static void main(String[] args) {
        return;
    }
}

D 程序员只需在文本文件的开头放置一个 shebang,而不会破坏语法:

#!/usr/bin/rdmd
void main(){}

见:

【讨论】:

  • 抱歉回滚。
  • 要将参数传递给可执行的 c++ 代码,在 ./a.out 之后添加“$@”;这样会是 ------------------ -------------- //usr/bin/clang++ -O3 -std=c++11 "$0" && ./a.out "$@";退出
  • 很好的答案。我也会检查现有的二进制文件是否过时。
  • @RaúlSalinas-Monteagudo 在示例 4 中确实可以发生,stat -c %y a.out main.c 比较会有所改善。介于 1 和 2 之间的版本看起来已经准备好生产了,哈哈。
  • 所以 unix 允许两种形式的 shebang #!//。知道记录在哪里吗?
【解决方案2】:

对于 C,您可以查看 tcc,即 Tiny C 编译器。将 C 代码作为脚本运行是其可能的用途之一。

【讨论】:

  • 哦,我喜欢这个。您所要做的就是添加#!/usr/bin/tcc -run
  • @Brendan:我也很喜欢 tcc,尤其是在编译时间方面;不过,您必须小心编译器错误
【解决方案3】:
$ cat /usr/local/bin/runc
#!/bin/bash
sed -n '2,$p' "$@" | gcc -o /tmp/a.out -x c++ - && /tmp/a.out
rm -f /tmp/a.out

$ cat main.c
#!/bin/bash /usr/local/bin/runc

#include <stdio.h>

int main() {
    printf("hello world!\n");
    return 0;
}

$ ./main.c
hello world!

sed 命令获取.c 文件并去除hash-bang 行。 2,$p 表示将第 2 行打印到文件末尾; "$@" 扩展为 runc 脚本的命令行参数,即 "main.c"

sed 的输出通过管道传送到 gcc。将- 传递给 gcc 告诉它从标准输入读取,当你这样做时,你还必须用-x 指定源语言,因为它没有文件名可供猜测。

【讨论】:

  • @John Kugelman:当您打算使用sed 剥离它时,需要将#!/bin/bash /usr/local/bin/runc 作为main.c 的第一行吗?
  • @eSKay - 该行告诉系统使用什么程序来“运行”脚本。没有它,bash 将尝试将 .c 文件解释为 bash 脚本并以语法错误进行轰炸。
  • @John Kugelman:谢谢!我现在明白了。很聪明!
  • 您应该使用 mktemp 而不是 a.out 否则在同时运行两个不同的 C 脚本时会出现问题...此外,C 脚本不会编译为真正的 C 程序因为顶部的 hashbang,虽然我不确定你能做些什么......
  • @Graham 这就是为什么他去掉了 #!在编译之前使用sed
【解决方案4】:

由于 shebang 行将被传递给编译器,并且 # 表示预处理器指令,它会在 #! 上阻塞。

您可以做的是将 makefile 嵌入到 .c 文件中(如 this xkcd thread 中所述)

#if 0
make $@ -f - <<EOF
all: foo
foo.o:
   cc -c -o foo.o -DFOO_C $0
bar.o:
   cc -c -o bar.o -DBAR_C $0
foo: foo.o bar.o
   cc -o foo foo.o bar.o
EOF
exit;
#endif

#ifdef FOO_C

#include <stdlib.h>
extern void bar();
int main(int argc, char* argv[]) {
    bar();
    return EXIT_SUCCESS;
}

#endif

#ifdef BAR_C
void bar() {
   puts("bar!");
}
#endif

围绕 makefile 的 #if 0 #endif 对确保预处理器忽略该文本部分,并且 EOF 标记标记 make 命令应该停止解析输入的位置。

【讨论】:

  • 不是我想要的,但很接近,而且绝对很有趣。
【解决方案5】:

CINT:

CINT 是 C 和 C++ 的解释器 代码。它很有用,例如对于情况 哪里更快速发展 比执行时间重要。使用 编译和链接的解释器 周期大大缩短 促进快速发展。 CINT 甚至使 C/C++ 编程变得愉快 适合兼职程序员。

【讨论】:

  • 有趣。我希望能得到与 gcc $stuff 相同的结果; ./文件名虽然。这已经超出了我的预期。
  • Brendan - 如果你想这样做,你将不得不在 bash 等中编写一个脚本来编译和调用程序。
  • @Brendan:你为什么需要这个,真的吗?对我来说,以这种方式使用 C 和 C++ 没有多大意义
  • 没有必要,我只是想知道。这将是一种分发程序的有趣方式。
【解决方案6】:
#!/usr/bin/env sh
tail -n +$(( $LINENO + 1 )) "$0" | cc -xc - && { ./a.out "$@"; e="$?"; rm ./a.out; exit "$e"; }

#include <stdio.h>

int main(int argc, char const* argv[]) {
    printf("Hello world!\n");
    return 0;
}

这也正确地转发了参数和退出代码。

【讨论】:

    【解决方案7】:

    您可能想查看专为此设计的ryanmjacobs/c。它充当您最喜欢的编译器的包装器。

    #!/usr/bin/c
    #include <stdio.h>
    
    int main(void) {
        printf("Hello World!\n");
        return 0;
    }
    

    使用c 的好处是您可以选择要使用的编译器,例如

    $ export CC=clang
    $ export CC=gcc
    

    因此,您也可以获得所有您最喜欢的优化!击败tcc -run

    您还可以将编译器标志添加到 shebang,只要它们以 -- 字符终止:

    #!/usr/bin/c -Wall -g -lncurses --
    #include <ncurses.h>
    
    int main(void) {
        initscr();
        /* ... */
        return 0;
    }
    

    c 也使用$CFLAGS$CPPFLAGS(如果它们也已设置)。

    【讨论】:

    • 请注意,匿名建议编辑声称:“它现在支持缓存。在运行一次脚本后,第二次几乎是即时的。”
    【解决方案8】:

    很短的提议会利用:

    • 当前的 shell 脚本是未知类型的默认解释器(没有 shebang 或可识别的二进制标头)。
    • “#”是 shell 中的注释,“#if 0”是禁用代码。

      #if 0
      F="$(dirname $0)/.$(basename $0).bin"
      [ ! -f $F  -o  $F -ot $0 ] && { c++ "$0" -o "$F" || exit 1 ; }
      exec "$F" "$@"
      #endif
      
      // Here starts my C++ program :)
      #include <iostream>
      #include <unistd.h>
      
      using namespace std;
      
      int main(int argc, char **argv) {
          if (argv[1])
               clog << "Hello " << argv[1] << endl;
          else
              clog << "hello world" << endl;
      }
      

    然后你可以chmod +x你的.cpp文件然后./run.cpp

    • 您可以轻松地为编译器指定标志。
    • 二进制文件与源代码一起缓存在当前目录中,并在必要时更新。
    • 原始参数被传递给二进制文件:./run.cpp Hi
    • 它不重复使用a.out,因此您可以在同一个文件夹中拥有多个二进制文件。
    • 使用系统中的任何 c++ 编译器。
    • 二进制文件以“.”开头以便它在目录列表中隐藏。

    问题:

    • 并发执行会发生什么?

    【讨论】:

    • 直到知道省略 shebang 会退回到 /bin/sh,我才知道,这是经验还是规范?
    • 根据 exec 的 man:“如果文件头无法识别(尝试的 execve(2) 失败并出现错误 ENOEXEC),这些函数将执行 shell (/bin/sh)将文件的路径作为其第一个参数。(如果此尝试失败,则不再进行搜索。)“这意味着它是可以接受的,并且由系统调用 execve() 本身完成。你碰巧在 /bin/sh 里有什么,你不知道。
    【解决方案9】:

    John Kugelman 的变体可以这样写:

    #!/bin/bash
    t=`mktemp`
    sed '1,/^\/\/code/d' "$0" | g++ -o "$t" -x c++ - && "$t" "$@"
    r=$?
    rm -f "$t"
    exit $r
    
    
    //code
    #include <stdio.h>
    
    int main() {
        printf("Hi\n");
        return 0;
    }
    

    【讨论】:

    • 这不仅仅是一种变体。它是一个单一的文件实现,看起来不错。我遇到的主要问题是,如果你在 C++ IDE 中加载它,它会发疯。您无法隐藏 shebang,但是如果您在 shebang 之后添加 : /* 并在 exit 命令之后添加 #*/,那么 IDE 将忽略所有其他 bash 内容。
    • @Guss,让 shebang 看起来像 #!/bin/bash : /* 会导致查找文件 : /* 时出错,因为命令行现在看起来像 /bin/bash : /* ./man.c。此外,我相信某些 IDE 会报告不正确的预处理器指令 #!/bin/bash ... 失败。
    • 抱歉我的评论不够清楚。我的意思是在 shebang 行之后添加: /* 作为以下行。关于不喜欢 shebang 的 IDE(或以 : 开头的下一行 - 是的,可能。我通常使用的 IDE 将这些标记为错误,但除此之外不要大惊小怪,文件的其余部分已正确解析.
    • @Guss,真的。这会将大部分 bash 代码标记为注释。使用@Ephphatha 建议的#if 0#endif 也可以。
    • 是的,但是像 sed 表达式那样对编译器隐藏 shell 代码是没有意义的——你可以只给 gcc 整个文件(减去 shebang)。然后标题可能看起来像这样(注意文本中的“\n”,cmets 不是面向行的):#!/bin/bash\n#if 0\n t=$(mktemp -u);g++ -o $t -x c++ &lt;(tail -n+2 $0) &amp;&amp; $t "$@"; r=$?; rm -f $t; exit $r\n#endif
    【解决方案10】:

    这是另一种选择:

    #if 0
    TMP=$(mktemp -d)
    cc -o ${TMP}/a.out ${0} && ${TMP}/a.out ${@:1} ; RV=${?}
    rm -rf ${TMP}
    exit ${RV}
    #endif
    
    #include <stdio.h>
    
    int main(int argc, char *argv[])
    {
      printf("Hello world\n");
      return 0;
    }
    

    【讨论】:

      【解决方案11】:

      我知道这个问题不是最近的问题,但我还是决定将我的答案混入其中。 使用 Clang 和 LLVM,无需写出中间文件或调用外部帮助程序/脚本。 (除了clang/clang++/lli)

      您可以将 clang/clang++ 的输出通过管道传输到 lli。

      #if 0
      CXX=clang++
      CXXFLAGS="-O2 -Wall -Werror -std=c++17"
      CXXARGS="-xc++ -emit-llvm -c -o -"
      CXXCMD="$CXX $CXXFLAGS $CXXARGS $0"
      LLICMD="lli -force-interpreter -fake-argv0=$0 -"
      $CXXCMD | $LLICMD "$@" ; exit $?
      #endif
      
      #include <cstdio>
      
      int main (int argc, char **argv) {
        printf ("Hello llvm: %d\n", argc);
        for (auto i = 0; i < argc; i++) {
          printf("%d: %s\n", i, argv[i]);
        }
        return 3==argc;
      }
      

      但是,以上内容不允许您在 c/c++ 脚本中使用标准输入。 如果 bash 是您的 shell,那么您可以执行以下操作来使用标准输入:

      #if 0
      CXX=clang++
      CXXFLAGS="-O2 -Wall -Werror -std=c++17"
      CXXARGS="-xc++ -emit-llvm -c -o -"
      CXXCMD="$CXX $CXXFLAGS $CXXARGS $0"
      LLICMD="lli -force-interpreter -fake-argv0=$0"
      exec $LLICMD <($CXXCMD) "$@"
      #endif
      
      #include <cstdio>
      
      int main (int argc, char **argv) {
        printf ("Hello llvm: %d\n", argc);
        for (auto i = 0; i < argc; i++) {
          printf("%d: %s\n", i, argv[i]);
        }
        for (int c; EOF != (c=getchar()); putchar(c));
        return 3==argc;
      }
      

      【讨论】:

        【解决方案12】:

        有几个地方建议 shebang (#!) 应该保留,但它对于 gcc 编译器是非法的。所以有几个解决方案把它删掉了。此外,可以插入预处理器指令,在 c 代码错误的情况下修复编译器消息。

        #!/bin/bash
        #ifdef 0 
        xxx=$(mktemp -d)
        awk 'BEGIN 
         { print "#line 2 \"$0\""; first=1; } 
         { if (first) first=0; else print $0 }' $0 |\
        g++ -x c++ -o ${xxx} - && ./${xxx} "$@"
        rv=$?
        \rm ./${xxx}
        exit $rv
        #endif
        #include <iostream>
        int main(int argc,char *argv[]) { 
          std::cout<<"Hello world"<<std::endl; 
        }
        

        【讨论】:

          【解决方案13】:

          如上一个答案所述,如果您使用 tcc 作为编译器,则可以将 shebang #!/usr/bin/tcc -run 作为源文件的第一行。

          但是,有一个小问题:如果你想编译同一个文件,gcc 将抛出一个error: invalid preprocessing directive #!tcc 将忽略 shebang 并编译得很好)。

          如果您仍然需要使用gcc 进行编译,一种解决方法是使用tail 命令从源文件中切断shebang 行,然后再将其通过管道传输到gcc

          tail -n+2 helloworld.c | gcc -xc -
          

          请记住,所有警告和/或错误都会消失一行。

          您可以通过创建一个 bash 脚本来自动检查文件是否以 shebang 开头,例如

          if [[ $(head -c2 $1) == '#!' ]]
          then
            tail -n+2 $1 | gcc -xc -
          else
            gcc $1
          fi
          

          并使用它来编译您的源代码,而不是直接调用gcc

          【讨论】:

            【解决方案14】:

            只是想分享一下,感谢 Pedro 对使用 #if 0 技巧的解决方案的解释,我已经更新了我在 TCC (Sugar C) 上的 fork,以便最终可以使用 shebang 调用所有示例,在查找源代码时没有错误在 IDE 上。

            现在,在 VS Code 中使用 clangd 为项目源代码显示精美的代码。示例第一行如下所示:

            #if 0
              /usr/local/bin/sugar `basename $0` $@ && exit;
              // above is a shebang hack, so you can run: ./args.c <arg 1> <arg 2> <arg N>
            #endif
            

            这个项目的初衷一直是使用 C,就像使用 TCC 底层的脚本语言一样,但客户端优先考虑 ram 输出而不是文件输出(没有 -run 指令)。

            您可以通过以下方式查看该项目:https://github.com/antonioprates/sugar

            【讨论】:

              猜你喜欢
              • 1970-01-01
              • 2010-12-29
              • 1970-01-01
              • 2019-04-15
              • 1970-01-01
              • 2014-09-12
              • 1970-01-01
              • 2011-02-24
              相关资源
              最近更新 更多