How can I get rid of this osascript output? - macos

The following question relates to an answer that was posted on this question:
I like the notion of creating my own function that opens a new terminal, so the script that Craig Walker linked to in that above-referenced question suited my needs. The script, written by Mark Liyanage, is found here.
That script is this:
#!/bin/sh
#
# Open a new Mac OS X terminal window with the command given
# as argument.
#
# - If there are no arguments, the new terminal window will
# be opened in the current directory, i.e. as if the command
# would be "cd `pwd`".
# - If the first argument is a directory, the new terminal will
# "cd" into that directory before executing the remaining
# arguments as command.
# - If there are arguments and the first one is not a directory,
# the new window will be opened in the current directory and
# then the arguments will be executed as command.
# - The optional, leading "-x" flag will cause the new terminal
# to be closed immediately after the executed command finishes.
#
# Written by Marc Liyanage <http://www.entropy.ch>
#
# Version 1.0
#
if [ "x-x" = x"$1" ]; then
EXIT="; exit"; shift;
fi
if [[ -d "$1" ]]; then
WD=`cd "$1"; pwd`; shift;
else
WD="'`pwd`'";
fi
COMMAND="cd $WD; $#"
#echo "$COMMAND $EXIT"
osascript 2>/dev/null <<EOF
tell application "Terminal"
activate
do script with command "$COMMAND $EXIT"
end tell
EOF
I made one change to the script on the linked site; I commented out the line that outputs "$COMMAND $EXIT" to eliminate some verbosity. However, when I run the script I still get this output
tab 1 of window id 2835
just before it opens the new window and executes the command that I pass in. Any ideas why this would be happening? (I tried moving the redirect of stderr to /dev/null before the call to oascript, but that made no difference.)

tab 1 of window 2835 is the AppleScript representation of the object returned by the do script command: it is the tab instance created to execute the command. osascript returns the results of the script execution to standard output. Since there is no explicit return in the AppleScript script, the returned value of the whole script is the result of the last-executed statement, normally the do script command. The two easiest fixes are to either redirect stdout of the osascript (and preferably not redirect stderr in case of errors):
osascript >/dev/null <<EOF
or insert an explicit return (with no value) into the AppleScript.
tell application "Terminal"
activate
do script with command "$COMMAND $EXIT"
end tell
return

Related

Bindsym does not execute i3wm command

This shortcut does not work in i3wm. It's supposed to show the window list of open apps.
Nothing visible happens, when keyboard shortcut is pressed.
bindsym $mod+space exec bash -c "/home/george/./dmenu-i3-window-jumper.sh"
However the script runs fine from terminal.
The bash code for the script:
https://github.com/minos-org/minos-desktop-tools/blob/master/tools/dmenu-i3-window-jumper
This is a two side issue
First some small config stuff:
I think you got an extra dot in there as ./ in that context just represents the folder preceding it (i.e: /home/george)
You can use the $HOME variable as a stand in for your home folder, i3 will pick it up
I would argue there is really no need for the bash -c, since your file has both a .sh extension and a #!/bin/sh header on the first line, which means you just need to give it execution permissions with chmod +x and it will run with bash anyways.
So in synthesis, you gotta
chmod +x /home/george/dmenu-i3-window-jumper.sh
so the script can be run without calling bash directly,
and your bindsym could be simplified to
bindsym $mod+space exec "$HOME/dmenu-i3-window-jumper.sh"
And then there is the script stuff:
You see, around line 44 the script checks to see if the STDIN is in a terminal, if its not then it tries to pipe a file to the arg array
if [ ! -t 0 ]; then
#add input comming from pipe or file to $#
set -- "${#}" $(cat)
fi
This seems to be the main problem, since you're not running the command in a terminal and you're not giving it a file either.
Your options are A: changing the if so it will always pass an empty string to the argument array
if [ ! -t 0 ]; then
#add input comming from pipe or file to $#
set -- "${#}" ""
fi
or B: create a dummy file with touch ~/dummy and then pass it to the script on the bindsym
bindsym $mod+space exec "$HOME/dmenu-i3-window-jumper.sh < $HOME/dummy"
Both seem to work fine on my setup, good luck!

bash hangs when exec > > is called and an additional bash script is executed with output to stdin [duplicate]

I have a shell script which writes all output to logfile
and terminal, this part works fine, but if I execute the script
a new shell prompt only appear if I press enter. Why is that and how do I fix it?
#!/bin/bash
exec > >(tee logfile)
echo "output"
First, when I'm testing this, there always is a new shell prompt, it's just that sometimes the string output comes after it, so the prompt isn't last. Did you happen to overlook it? If so, there seems to be a race where the shell prints the prompt before the tee in the background completes.
Unfortunately, that cannot fixed by waiting in the shell for tee, see this question on unix.stackexchange. Fragile workarounds aside, the easiest way to solve this that I see is to put your whole script inside a list:
{
your-code-here
} | tee logfile
If I run the following script (suppressing the newline from the echo), I see the prompt, but not "output". The string is still written to the file.
#!/bin/bash
exec > >(tee logfile)
echo -n "output"
What I suspect is this: you have three different file descriptors trying to write to the same file (that is, the terminal): standard output of the shell, standard error of the shell, and the standard output of tee. The shell writes synchronously: first the echo to standard output, then the prompt to standard error, so the terminal is able to sequence them correctly. However, the third file descriptor is written to asynchronously by tee, so there is a race condition. I don't quite understand how my modification affects the race, but it appears to upset some balance, allowing the prompt to be written at a different time and appear on the screen. (I expect output buffering to play a part in this).
You might also try running your script after running the script command, which will log everything written to the terminal; if you wade through all the control characters in the file, you may notice the prompt in the file just prior to the output written by tee. In support of my race condition theory, I'll note that after running the script a few times, it was no longer displaying "abnormal" behavior; my shell prompt was displayed as expected after the string "output", so there is definitely some non-deterministic element to this situation.
#chepner's answer provides great background information.
Here's a workaround - works on Ubuntu 12.04 (Linux 3.2.0) and on OS X 10.9.1:
#!/bin/bash
exec > >(tee logfile)
echo "output"
# WORKAROUND - place LAST in your script.
# Execute an executable (as opposed to a builtin) that outputs *something*
# to make the prompt reappear normally.
# In this case we use the printf *executable* to output an *empty string*.
# Use of `$ec` is to ensure that the script's actual exit code is passed through.
ec=$?; $(which printf) ''; exit $ec
Alternatives:
#user2719058's answer shows a simple alternative: wrapping the entire script body in a group command ({ ... }) and piping it to tee logfile.
An external solution, as #chepner has already hinted at, is to use the script utility to create a "transcript" of your script's output in addition to displaying it:
script -qc yourScript /dev/null > logfile # Linux syntax
This, however, will also capture stderr output; if you wanted to avoid that, use:
script -qc 'yourScript 2>/dev/null' /dev/null > logfile
Note, however, that this will suppress stderr output altogether.
As others have noted, it's not that there's no prompt printed -- it's that the last of the output written by tee can come after the prompt, making the prompt no longer visible.
If you have bash 4.4 or newer, you can wait for your tee process to exit, like so:
#!/usr/bin/env bash
case $BASH_VERSION in ''|[0-3].*|4.[0-3]) echo "ERROR: Bash 4.4+ needed" >&2; exit 1;; esac
exec {orig_stdout}>&1 {orig_stderr}>&2 # make a backup of original stdout
exec > >(tee -a "_install_log"); tee_pid=$! # track PID of tee after starting it
cleanup() { # define a function we'll call during shutdown
retval=$?
exec >&$orig_stdout # Copy your original stdout back to FD 1, overwriting the pipe to tee
exec 2>&$orig_stderr # If something overwrites stderr to also go through tee, fix that too
wait "$tee_pid" # Now, wait until tee exits
exit "$retval" # and complete exit with our original exit status
}
trap cleanup EXIT # configure the function above to be called during cleanup
echo "Writing something to stdout here"

How can I start a subscript within a perpetually running bash script after a specific string has been printed in the terminal output?

Specifics:
I'm trying to build a bash script which needs to do a couple of things.
Firstly, it needs to run a third party script that I cannot manipulate. This script will build a project and then start a node server which outputs data to the terminal continually. This process needs to continue indefinitely so I can't have any exit codes.
Secondly, I need to wait for a specific line of output from the first script, namely 'Started your app.'.
Once that line has been output to the terminal, I need to launch a separate set of commands, either from another subscript or from an if or while block, which will change a few lines of code in the project that was built by the first script to resolve some dependencies for a later step.
So, how can I capture the output of the first subscript and use that to run another set of commands when a particular line is output to the terminal, all while allowing the first script to run in the terminal, and without using timers and without creating a huge file from the output of subscript1 as it will run indefinitely?
Pseudo-code:
#!/usr/bin/env bash
# This script needs to stay running & will output to the terminal (at some point)
# a string that we need to wait/watch for to launch subscript2
sh subscript1
# This can't run until subscript1 has output a particular string to the terminal
# This could be another script, or an if or while block
sh subscript2
I have been beating my head against my desk for hours trying to get this to work. Any help would be appreciated!
I think this is a bad idea — much better to have subscript1 changed to be automation-friendly — but in theory you can write:
sh subscript1 \
| {
while IFS= read -r line ; do
printf '%s\n' "$line"
if [[ "$line" = 'Started your app.' ]] ; then
sh subscript2 &
break
fi
done
cat
}

Why isn't this command returning to shell after &?

In Ubuntu 14.04, I created the following bash script:
flock -nx "$1" xdg-open "$1" &
The idea is to lock the file specified in $1 (flock), then open it in my usual editor (xdg-open), and finally return to prompt, so I can open other files in sequence (&).
However, the & isn't working as expected. I need to press Enter to make the shell prompt appear again. In simpler constructs, such as
gedit test.txt &
it works as it should, returning the prompt immediately. I think it has to do with the existence of two commands in the first line. What am I doing wrong, please?
EDIT
The prompt is actually there, but it is somehow "hidden". If I issue the command
sudo ./edit error.php
it replies with
Warning: unknown mime-type for "error.php" -- using "application/octet-stream"
Error: no "view" mailcap rules found for type "application/octet-stream"
Opening "error.php" with Geany (application/x-php)
__
The errors above are not related to the question. But instead of __ I see nothing. I know the prompt is there because I can issue other commands, like ls, and they work. But the question remains: WHY the prompt is hidden? And how can I make it show normally?
Why isn't this command returning to shell after &?
It is.
You're running a command in the background. The shell prints a new prompt as soon as the command is launched, without waiting for it to finish.
According to your latest comment, the background command is printing some message to your screen. A simple example of the same thing:
$ echo hello &
$ hello
The cursor is left at the beginning of the line after the $ hello.
As far as the shell is concerned, it's printed a prompt and is waiting a new command. It doesn't know or care that a background process has messed up your display.
One solution is to redirect the command's output to somewhere other than your screen, either to a file or to /dev/null. If it's an error message, you'll probably have to redirect both stdout and `stderr.
flock -nx "$1" xdg-open "$1" >/dev/null 2>&1 &
(This assumes you don't care about the content of the message.)
Another option, pointed out in a comment by alvits, is to sleep for a second or so after executing the command, so the message appears followed by the next shell prompt. The sleep command is executed in the foreground, delaying the printing of the next prompt. A simple example:
$ echo hello & sleep 1
hello
[1] + Done echo hello
$
or for your example:
flock -nx "$1" xdg-open "$1" & sleep 1
This assumes that the error message is printed in the first second. That's probably a valid assumption for you example, but it might not be in general.
I don't think the command is doing what you think it does.
Have you tried to run it twice to see if the lock cannot be obtained the second time.
Well, if you do it, you will see that it doesn't fail because xdg-open is forking to exec the editor. Also if it fails you expect some indication.
You should use something like this
flock -nx "$1" -c "gedit '$1' &" || { echo "ERROR"; exit 1; }

How do you open a terminal with a specific path already cd'ed to?

How do I use the terminal to open another terminal window but with a path I specify?
I am using automator to load my work stuff when I get to work, but I need to know how to do this:
Open Terminal and Type:
• cd Work/Company/Project/
• script/server
And then new tab in that terminal window and cd to the same folder.
This opens a new terminal window from a command prompt on Mac OSX , executes "cd /" and then keeps the window on top:
osascript -e 'tell application "terminal"' -e 'do script "cd /"' -e 'end tell'
You can put this into a script like this:
#!/bin/sh
osascript -e 'tell application "terminal"' -e "do script \"cd $1\"" -e 'end tell'
Hope this helps.
Use an applescript to do this.
e.g. Open Terminal Here
You can write a shell script to cd to that directory
So write a script that executes something like cd /user/music or something like that, save it as myscript.sh and run it using chmod +x myscript.sh.
This resource from the OS X developer network is pretty helpful
The two scripts below together handle the common scenarios:
1) If Terminal is already running, open a new terminal window and run the 'cd mydir' there
2) If terminal is not already running, use the initial window that Terminal spawns (window 0), rather than annoyingly launching a second window
NOTE: what's not quite perfect is if Terminal has several windows open, all of them will be brought to the front, overlapping any other apps. A solution to raising only the last terminal window to the front appears to require the black magic of AppleScriptObjC - references below:
https://apple.stackexchange.com/questions/39204/script-to-raise-a-single-window-to-the-front
http://tom.scogland.com/blog/2013/06/08/mac-raise-window-by-title/
Script 1 - open a text editor and save as:
/usr/local/bin/terminal-here.sh
#!/bin/sh
osascript `dirname $0`/terminal-here.scpt $1 > /dev/null 2> /dev/null
Script 2 - open 'AppleScript Editor', paste contents below and save as:
/usr/local/bin/terminal-here.scpt
# AppleScript to cd (change directory) to a path passed as an argument
# If Terminal.app is running, the script will open a new window and cd to the path
# If Terminal.app is NOT running, we'll use the window that Terminal opens automatically on launch
# Run script with passed arguments (if any)
on run argv
if (count of argv) > 0 then
# There was an argument passed so consider it to be the path
set mypath to item 1 of argv
else
# Since no argument was passed, default to the home directory
set mypath to "~"
end if
tell application "System Events"
if (count (processes whose bundle identifier is "com.apple.Terminal")) is 0 then
# Terminal isn't running so we'll make sure to run the 'cd' in Terminal's first window (0)
tell application "/Applications/Utilities/Terminal.app"
# Turn off echo, run the 'cd', clear screen, empty the scrollback, re-enable echo
do script "stty -echo; cd " & (mypath as text) & ";clear; printf \"\\e[3J\"; stty echo" in window 0
activate last window
end tell
else
# Terminal is already running so we'll let it open a new window for our 'cd' command
tell application "/Applications/Utilities/Terminal.app"
# Turn off echo, run the 'cd', clear screen, empty the scrollback, re-enable echo
do script "stty -echo; cd " & (mypath as text) & ";clear; printf \"\\e[3J\"; stty echo"
activate last window
end tell
end if
end tell
end run

Resources