How to Use Execute Shell Script -- With Input From

Can I please see an example of how to use the "With Input From" parameter on the Execute Shell Script macro?

Set variable DogName to Bob
Execute Shell Script with input from variable DogName
echo

I was expecting to see "Bob".

I know that I can use echo $KMVAR_DogName but I want to know how to use the "With input from" parameter please.

4 Likes

Here's a more complicated example:

REF: Find (Search) For Files Using Bash Find [Example]
image

#IFS=$'\n'
{ read myFolderToSearch; read myRootFileName; read myFileExt; }

# --- DEFAULT VALUES If NOT Passed Via stdin ---
myFolderToSearch=${myFolderToSearch:-~/Documents}
myRootFileName=${myRootFileName:-*}
myFileExt=${myFileExt:-*}

myFolderToSearch="${myFolderToSearch/#~/$HOME}"

echo "$myFolderToSearch -- $myRootFileName.$myFileExt"

find "$myFolderToSearch" -maxdepth 1 -name "$myRootFileName.$myFileExt" 

# --->find /Users/jmichaeltx/Documents/Test -maxdepth 1 -name *.jp*g

Builds this command:
find /Users/jmichaeltx/Documents/Test -maxdepth 1 -name *.jp*g

2 Likes

@thankful_dude

StdIn is expected to be input to a process expecting it. For instance if you use the example at the top of the documentation (tr a-z A-Z), the contents of dogName ("Bob") would be output as "BOB".

1 Like

Hey Leonardo,

That's confusing isn't it? :wink:

As Scott says above – echo doesn't directly support STDIN – so you have to use a command that does.

Like this for instance:

image

Or it could written it like this:

cat | sed -n '1,3p'

Or just:

cat

And display the result in a window for testing.

@CJK shows you how to read into a specific shell variable, and @JMichaelTX gives you a more sophisticated version.

The cat method lets you read the entire STDIN value and pipe that through other commands without the interim step of saving to a variable.

-Chris

Chris, what if you have 3 different variables to pass thru STDIN? Can the cat command handle that?

I'm not Chris, but I can illustrate an answer:

which outputs:

image

So isolating and manipulating individual segments of data is not a problem, if you imagine replacing A, B, and C with your KM variable tokens.

As an aside, this example utilises $(</dev/stdin) to read the contents of stdin in much the same sort of way cat does. I don't recall any amazing differences in the two, other than this being a faster command to execute than cat (presumably because it isn't executing a separate program but instead reading from a file descriptor).

The above command:

printf '%s\t' $(</dev/stdin)

would be equivalent to:

printf '%s\t' $(cat)

Both snippets labelled as shell scripts which, naturally, initiates completely random syntax-highlighting for some unknown reason

1 Like

So, if I understand correctly, that essentially executes the main bash command of printf '%s\t' for each LINE in the stdin.

What if I needed "A", "B", and "C" to all be use in separate parameters/commands in the bash command?

IOW, is there a better way, using cat or /dev/stdin, in something like this:
find "$myFolderToSearch" -maxdepth 1 -name "$myRootFileName.$myFileExt"

Correct, in this particular instance. More generally, printf, like echo, will delimit at any character defined in the $IFS shell variable, which I think defaults to $'\n \t', i.e. newline, space, and tab. In hindsight, using single characters was a bit of a limited test case. Were the input to stdin this:

A B C
1 2 3
a b c

that same expression from earilier would produce:

A	B	C	1	2	3	a	b	c

This might be useful in some situations, but not ideal for our needs here. However, you can set the $IFS variable to easily ensure you get the desired result:

IFS=$'\n'
printf '%s\t' $(<\dev\stdin) 

Output:

A B C	1 2 3	a b c

If you’re after a one-line method that lets you reference the data as variables as it's being read from stdin, I don’t think you can. The variables need to be read and assigned first, as far as know. Here are the different ways I can think of doing this, using the same stdin in each case:

stdin:

A B C
1 2 3
a b c

1. cat

IFS=$'\n' # set the IFS variable to delimit at newlines only
vars=( $(cat) ) # the ($(...)) notation creates an array
printf '%s\n' "vars[0] : ${vars[0]}" "vars[1] : ${vars[1]}" "vars[2] : ${vars[2]}"

output:

vars[0] : A B C
vars[1] : 1 2 3
vars[2] : a b c

2. /dev/stdin (esesntially the same as cat)

IFS=$'\n'
vars=( $(</dev/stdin) )
printf '%s\n' "vars[0] : ${vars[0]}" "vars[1] : ${vars[1]}" "vars[2] : ${vars[2]}"

output:

vars[0] : A B C
vars[1] : 1 2 3
vars[2] : a b c

3. read

IFS=$'\n' read -d \n -a vars
printf '%s\n' "var[0] : ${vars[0]}" "var[1] : ${vars[1]}" "var[2] : ${vars[2]}"

output:

vars[0] : A B C
vars[1] : 1 2 3
vars[2] : a b c

Perhaps someone else (like @ccstone who is more experienced with shell scripting than I) might be able to offer additional insight into this. I actually don't use bash as my main shell, and only write bash scripts for use in Keyboard Maestro. I use FiSH, which in many ways, is a lot more intuitive, so lets me process the stdin from above using the read command like this:

read --line var1 var2 var3

without having to fuss with the $IFS business. It even lets me do shorthand like this:

read --line var{1,2,3}

which is functionally identical to the previous statement


And, of course you know how do bring in KM variables do the shell action with $KMVAR_{var}.

2 Likes

Thanks for the thoughtful discussion, @CJK. :+1:
Some very useful techniques here.