Open Terminal window for OSX System.Diagnostics.Process

Hi all,

I have a script that I’m using to upload to Itch.io after a build processes. It works perfectly on Windows, and works on OSX (it uploads), however it doesn’t open a Terminal window to actually show the upload’s progress. (On Windows it opens a CMD window.)

Here is the code I’m using:

Process uploadProc = new Process {
    StartInfo = {
        FileName = butlerPath,
        Arguments = "push \"" + projectFolder + "\" " + Settings.ItchURLUserName + "/" + Settings.ItchURLProjectName + ":" + "osx",
        CreateNoWindow = false,
        WindowStyle = ProcessWindowStyle.Normal,
        UseShellExecute = false
    }
};
  
uploadProc.Start();

So like I said, it does eventually upload. I just have no idea at what stage it is while it’s doing it. I tried to run Terminal instead (which does open a window), but I couldn’t figure out how to run the Unix executable using C# through the Terminal process.

Any help is greatly appreciated!

Hi

I haven´t tested but I guess it can work when called like this:

If you add the extension “.command” to a executable terminal script, the System should open it with Terminal.
Make sure the file has “x” permissions, you can test by double click on it in Finder.

Use the full path to your .command file instead of “TextEdit”, in the example.

Hey there,

Thanks for the response. I tried to add ‘.command’ to the end but unfortunately it threw an error:

However double clicking it in Finder works correctly and opens a Terminal window.

So… I just got myself a OSX machine. I have a heavy linux/windows background, but never could get into OSX (primarily because of price, that and I always found the user experience to be annoying).

But a buddy brought home one for me for free that he found in storage. It’s pretty nice at that (it’s only 2 years old, i5 quad core with 8 gigs RAM… yay!)

So I figured this would be a fairly simple problem to solve. Just open /bin/bash, though yeah, that probably wouldn’t have a gui for it. So something like xterm… what does OSX use???

Something called:
/Applications/Utilities/Terminal.app

OK… open that.

Nope, no work. Turns out .app files are just folders (I remember this when making osx builds, it just creates a folder on windows/linux).

OK… the actual program is at /Applications/Utilities/Terminal.app/Contents/MacOS/Terminal.

Open that… yay, we have a window!

OK… lets redirect that input so I can send it some commands… nope, commands won’t send. I guess Terminal doesn’t actually deal with a standard input pipe.

OK… so can I send it arguments?

Not exactly… I can’t pass in an shell command as the argument and it run that command.

BUT!

If I pass it a shell script location, it will run a shell script.

OK… write up a shell script, make it executable, something like this:

echo Hello World
sleep 5
echo Good-bye

Stick it in my home folder, chmod it 777 (for good measure), and:

public class TestRunProc : MonoBehaviour
{
    void Update()
    {
        if(Input.GetMouseButtonDown(0))
        {
            Debug.Log("RUNNING PROC");
          
            System.Diagnostics.Process.Start(@"/Applications/Utilities/Terminal.app/Contents/MacOS/Terminal",@"~/dostuff.sh");
        }
    }
}

And it worked.

Not sure if there’s a better solution. I’m not super familiar with OSX yet, so this is the best I could come up with.

I will say, yep, still convinced the user experience of OSX is garbage.

Note I chmod’d it 777 just to make sure any and all users could execute the file.

I’d advise probably setting it something more strict. I just wanted to make sure no permissions were getting in my way of testing.

@lordofduct thanks very much for the investigation! I hope asking for further help isn’t pushing my luck, hehe.

Unfortunately I can’t get the shell script to run, here’s the code I’m using. It does open a Terminal window, but it doesn’t actually execute anything:

            string itchShellPath = BuildMenu.GetBuildLocation() + "/itch.sh";
            FileHelpers.DeleteFile(itchShellPath);
           
            using (StreamWriter file = new StreamWriter(itchShellPath, false, Encoding.UTF8)) {
                file.WriteLine("");
                file.WriteLine("echo Hello World");
                file.WriteLine("sleep 5");
                file.WriteLine("echo Good bye");
            }
           
            System.Diagnostics.Process.Start(
                @"/Applications/Utilities/Terminal.app/Contents/MacOS/Terminal",
                itchShellPath
            );

However even once I manage that, I’m not sure what to enter into the file. Previously I would run Butler and provide arguments, I assume in this .sh file I can do both of these things, but I’m not sure what to actually write. I know the path to the Butler file, and I know the arguments I want to provide, but… would you be able to help me with how to execute that?

Thanks for your help so far!

Well… you just type the commands in the shell file just like you would in a terminal window. If you know how to type it in terminal, you know how to write a shell script.

So, looking at the butler article on itch.io (nice, I use itch too, here’s our page):
Installing butler · The butler manual - itch.io

It suggests adding butler to your path if you want…

But if you know the path you can just directly reference it. And on Mac OS it says it should be at:

~/Library/Application Support/itch/bin/butler

I don’t know what commands you use, if you show me I can help. But following the push example here:
https://itch.io/docs/butler/pushing.html

You’d just say:

~/Library/Application Support/itch/bin/butler push directory user/game:channel

So if say, I had my game at ~/Projects/PrototypeMansion/Builds, I’d say something like:

~/Library/Application Support/itch/bin/butler push ~/Projects/PrototypeMansion/Builds/PrototypeMansion_OSX.zip myjlhusername/prototypemansion:osxuniversal

I could even throw in some other stuff, like say my build wasn’t zipped/tarred yet, I could tar it before pushing it:

tar czf ~/Projects/PrototypeMansion/Builds/PrototypeMansion_OSX.tar.gz ~/Projects/PrototypeMansion/Builds/PrototypeMansion.app
~/Library/Application Support/itch/bin/butler push ~/Projects/PrototypeMansion/Builds/PrototypeMansion_OSX.tar.gz myjlhusername/prototypemansion:osxuniversal

@lordofduct Oh excellent, thanks for that! I actually just edited my post as it turns out the commands aren’t actually executing. I’m trying to generate the shell command file dynamically, maybe that’s the issue?

You’re generating a script in C#. The file that results may be a shell script (ends with .sh)… but it’s not necessarily made an executable (remember the chmod I showed earlier).

You’ll need to first push a chmod on that file to do so.

Either 1)
Use Process to launch /bin/bash to push the chmod command.

Or 2)
Create an empty shell script in the target location and chmod it from terminal:

chmod +x /path/to/shellscript

Now that it’s already created and ready. Then instead of creating a file… you instead open this file and overwrite its contents with what you want.

Alright, it’s almost working perfectly! However the first line of my Shell script always fails. If for example I copy two identical file.WriteLine(“echo Hello”)s, the first will fail (as it’s the first line in the .sh file), but the second will succeed. And this is the case for anything I put in the first line of the file. “command not found”, no matter what it is. I tried adding a “#!/bin/sh” as the first line, but I get “No such file or directory”.

So I can just put an empty line there, and that fails but then the rest executes. However it does seem strange that it’s failing, I’m surely missing some required syntax? But I can’t find anything outside of the #!.

Itch is really great! I’m a big fan, and being able to upload binary diffs saves a lot of time. Distributing through the Itch app is also great for testers!

Here’s the code thus far, I’d love to solve why that first line is failing, but otherwise thanks so much for your help!

static void RunButlerOSX(string arguments, string butlerPath = "") {
    string itchShellScript = BuildMenu.GetBuildLocation() + "/itch.sh";
   
    FileHelpers.DeleteFile(itchShellScript);
   
    using (StreamWriter file = new StreamWriter(itchShellScript, false, Encoding.UTF8)) {
        file.WriteLine("#!/bin/sh");
//                file.WriteLine("printf \"\033c\"");
        file.WriteLine(string.Format("\"{0}\" {1}", butlerPath, arguments));
    }

    System.Diagnostics.Process chmod = new System.Diagnostics.Process {
        StartInfo = {
            FileName = @"/bin/bash",
            Arguments = string.Format("-c \"chmod 777 {0}\"", itchShellScript),
            UseShellExecute = false,
            CreateNoWindow = true
        }
    };

    chmod.Start();
    chmod.WaitForExit();
   
    System.Diagnostics.Process uploadProc = new System.Diagnostics.Process {
        StartInfo = {
            FileName = @"/Applications/Utilities/Terminal.app/Contents/MacOS/Terminal",
            Arguments = itchShellScript,
            UseShellExecute = false,
            CreateNoWindow = false,
            WindowStyle = System.Diagnostics.ProcessWindowStyle.Normal
        }
    };

    uploadProc.Start();
   
//            FileHelpers.DeleteFile(itchShellScript);
}

Ok. thats some workaround :). Is it some extra characters there (or missing) makeing it fail? Can you compare a shellscript made with StreamWriter with the same script made form the shell with for example vi or pico?

Having just gone through the very traumatic ordeal of trying to get something similar working myself, there is some very useful information in this thread - thank you all, I would almost certainly still be banging my head against a wall without it.

For the benefit of anyone else who may encounter this in the future, there are a few extra bits I’d like to add though:

Using Encoding.UTF8 in the construction of the StreamWriter objects results in the UTF8 Byte Order Mark (BOM) getting written as the first bytes in the .sh file. This is what’s causing the weird “command not found” problems on whatever the first line of the script is. Just using Encoding.ASCII fixes this, although if required then “new UTF8Encoding (false)” might also work (untested).

If your script file location has spaces in it, then you will need to wrap the Arguments string in additional quotes when constructing the uploadProc process. Otherwise you will just get a mysteriously blank Terminal. i.e. Arguments = string.Format ("\"{0}\"", itchShellScript),

I could not manage to successfully invoke chmod in the way described in this thread. Instead I managed to get it working using another method I found elsewhere. Pop these lines in your class:

  [DllImport ("libc", EntryPoint = "chmod", SetLastError = true)]
  private static extern int sys_chmod (string path, uint mode);

… and then you can change permissions on your script with:

sys_chmod (itchShellScript, 0x1ED);  // 0x1ED is 0755

Hi, I’ve been trying to do this from 2018.4.10f1 on Catalina 10.15, but the call
System.Diagnostics.Process.Start(@"/Applications/Utilities/Terminal.app/Contents/MacOS/Terminal");
returns the error:

Win32Exception: Cannot find the specified file
System.Diagnostics.Process.StartWithShellExecuteEx (System.Diagnostics.ProcessStartInfo startInfo) (at <d2957de1c3fd4781a43d89572183136c>:0)
System.Diagnostics.Process.Start () (at <d2957de1c3fd4781a43d89572183136c>:0)
(wrapper remoting-invoke-with-check) System.Diagnostics.Process.Start()
System.Diagnostics.Process.Start (System.Diagnostics.ProcessStartInfo startInfo) (at <d2957de1c3fd4781a43d89572183136c>:0)
System.Diagnostics.Process.Start (System.String fileName) (at <d2957de1c3fd4781a43d89572183136c>:0)

I’ve checked that the path is correct, does anybody know why this would happen ?
As a note, I’ve tried opening TextEdit in a similar way and it also fails with the same error.

2 Likes

Hey Thomas, Did you ever figure this out? I am running into the same issue!

You might not have checked very well, the path is /System/Applications/Utilities/Terminal.app on Catalina and Big Sur.

It worked!