您的问题与 Scala、Ubuntu 或 Hadoop 本身无关。这完全是关于 shell 脚本和理解 shell 的工作方式。
首先,您不需要调用 bash,在您的情况下,它只是另一个(不必要的)间接层。删除 bash 并留给您:
Process("su userA && hadoop fs -ls").!
...这将失败。为什么?因为这段代码等价于:
Process(Seq("su", "userA", "&&", "hadoop", "fs", "-ls")).!
如您所见,包括&& 在内的所有命令都作为命令行参数传递给su。自然su 不知道如何处理它,因此会失败。
现在让我们暂时忘记 Scala 代码,然后转到 bash shell 并尝试那里的命令。让我们使用whoami来测试用户是否已经变为userA:
su userA && whoami
您会注意到的第一件事是su 在以这种方式调用时会提供交互式 shell 提示符(首先要求输入密码,除非您从 root 运行它)。从程序运行su 时,这可能不是您想要的。此外,在您退出该 shell 后,您会看到用户并未更改为 userA。
原因如下。 有效用户的概念与流程相关。在给定的时间点,每个进程都只有一个有效用户。进程的初始有效用户继承自生成进程。
因此,当您从 bash 提示符运行 su userA && whoami 时会发生以下情况:
-
bash 生成 su 作为子进程,su 从其父进程继承有效用户:
bash (root)
+- su (root)
-
su 通过setuid 更改其有效用户:
bash (root)
+- su (userA)
-
默认情况下,su 生成指定用户的默认 shell,如在 /etc/passwd 中给出的子进程(例如 sh); sh 从其父级继承有效用户:
bash (root)
+- su (userA)
+- sh (userA)
现在让我们进入&& 部分。它原来是 shell 语法的一部分。 command1 && command2 要求 shell 先运行 command1 然后仅在 command1 存在且没有任何错误(即其退出代码为零)运行 命令2。了解两件事至关重要:
-
command2 仅在 command1 成功完成之后运行;
-
command1 和 command2 都是由同一个 shell 进程生成的,因此它们都继承了它的有效用户。
这就是我们通过su userA && whoami 调用得到的结果:
bash (root)
+- su (userA, already exited by the time whoami started)
| +- sh (userA, already exited by the time whoami started)
+- whoami (root)
因此,无论您通过su 更改为哪个用户,进程whoami 都不会受到影响。
为了解决这个问题,您应该让whoami 由su 生成,以便它可以继承更改后的用户。
要实现这一点,请像这样调用su:
su -c whoami
这将产生以下进程树:
bash (root)
+- su (userA)
+- whoami (userA)
现在让我们回到 Scala。不幸的是,如果你从 Scala 运行上面的命令,你会得到su: must be run from a terminal。老实说我不知道如何正确处理这个问题,所以我建议改用sudo。
所以最终的解决方案是:
Process(Seq("sudo", "-u", "userA", "hadoop fs -ls")).!