将C或C ++文件作为脚本运行

时间:2010-03-20 07:48:05

标签: c++ c shell

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

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

int main(){ return 0; }

但它说:

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

14 个答案:

答案 0 :(得分:26)

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

答案 1 :(得分:20)

简短回答:

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

诀窍是你的文本文件必须是有效的C / C ++代码和shell脚本。在解释器到达C / C ++代码之前,请记住shell脚本中的exit,或者调用exec magic。

使用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(){}

答案 2 :(得分:17)

$ 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告诉它从stdin读取,当你这样做时,你还必须用-x指定源语言,因为它没有可以猜出的文件名。

答案 3 :(得分:10)

由于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命令应停止解析输入的位置。

答案 4 :(得分:7)

CINT

  

CINT是C和C ++的解释器   码。它是有用的,例如对于情况   快速发展的地方更多   比执行时间重要。运用   解释器的编译和链接   周期大大减少   促进快速发展。 CINT   使C / C ++编程更加愉快   适合兼职程序员。

答案 5 :(得分:5)

您可能想要结帐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

答案 6 :(得分:5)

#!/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 :(得分:3)

约翰库格曼的变体可以用这种方式写成:

#!/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;
}

答案 8 :(得分:3)

一个简短的建议会利用:

  • 当前的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 {em} .cpp 文件,然后./run.cpp

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

问题:

  • 并发执行时会发生什么?

答案 9 :(得分:2)

这是另一种选择:

#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;
}

答案 10 :(得分:1)

我知道这个问题不是最近才提出来的,但是我还是决定把我的答案混在一起。 使用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 ++脚本中使用stdin。 如果bash是您的shell,则可以执行以下操作以使用stdin:

#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;
}

答案 11 :(得分:1)

有几个地方建议应该保留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; 
}

答案 12 :(得分:0)

如上一个答案所述,如果您使用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

答案 13 :(得分:0)

只是想分享一下,感谢 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