How to force bash/zsh to evaluate parameter as multiple arguments when applied to a command - command

I am trying to run a program like this:
$CMD $ARGS
where $ARGS is a set of arguments with spaces. However, zsh appears to be handing off the contents of $ARGS as a single argument to the executable. Here is a specific example:
$export ARGS="localhost -p22"
$ssh $ARGS
ssh: Could not resolve hostname localhost -p22: Name or service not known
Is there a bash or zsh flag that controls this behavior?
Note that when I put this type of command in a $!/bin/sh script, it performs as expected.
Thanks,
SetJmp

In zsh it's easy:
Use cmd ${=ARGS} to split $ARGS into separate arguments by word (split on whitespace).
Use cmd ${(f)ARGS} to split $ARGS into separate arguments by line (split on newlines but not spaces/tabs).
As others have mentioned, setting SH_WORD_SPLIT makes word splitting happen by default, but before you do that in zsh, cf. http://zsh.sourceforge.net/FAQ/zshfaq03.html for an explanation as to why whitespace splitting is not enabled by default.

If you want to have a string variable (there are also arrays) be split into words before passing to a command, use $=VAR. There is also an option (shwordsplit if I am not mistaking) that will make any command $VAR act like command $=VAR, but I suggest not to set it: I find it very inconvenient to type things like command "$VAR" (zsh: command $VAR) and command "${ARRAY[#]}" (zsh: command $ARRAY).

It will work if you use eval $CMD $ARGS.

Related

Adding Arguments to PowerShell Alias Functions

I just installed grep on PowerShell through chocolatey and using it, I could search for a string I want from any text as shown below:
However, I don't want to always have to type grep --color, hence I tried to make an alias on my $profile like this:
Function grep($a1){
grep --color $a1
}
but then I would get an error:
So I tried:
Set-Alias grep "grep --color"
but then I get yet another error:
I can't think of anything else to make this work, hence I'd really appreciate any help.
Aliases in PowerShell are mere alternative names for other commands, so you cannot use them to include arguments to pass to these other commands.
You therefore indeed need a function, but since you're naming it the same as the external program you're wrapping, you need to disambiguate so as to avoid an infinite recursion:
function grep {
$externalGrep = Get-Command -Type Application grep
if ($MyInvocation.ExpectingInput) { # pipeline (stdin) input present
# $args passes all arguments through.
$input | & $externalGrep --color $args
} else {
& $externalGrep --color $args
}
}
Note:
Use of the automatic $input variable to relay pipeline (stdin) input means that this input is collected (buffered) in full first. More effort is needed to implement a true streaming solution - see this answer for an example.
Alternatively - at least on Unix-like platforms - you can set an environment variable to control grep's coloring behavior, which may obviate the need for a function wrapper; the equivalent of the --color parameter is to set $env:GREP_COLOR='always' (the other supported values are 'never' and 'auto').

Passing a semicolon as a parameter to a .bat file from both cmd and PowerShell

This question seems to be rather simple, but even after searching the web for a couple of hours, I was not able to find a solution...
I have a batch file test.bat
set MY_VARIABLE=%~1
echo %MY_VARIABLE%
The point is that I want to call this programm with a semicolon as input parameter, i.e.,
.\test.bat ";",
from both cmd and Windows PowerShell. While this works fine from cmd, PowerShell does not seem to get anything as an input. Is there any way to make this work for both simultaneously?
This is because of command line syntax. The semicolon is one of multiple delimiters, that split the command line into words: a;b would be interpreted as two separate arguments a (%1) and b (%2).
Therefore, quotes are required. Since Powershell uses quotes for string literals (Powershell does its own re-quoting behind the scenes when passing arguments), you need to include them in the string:
.\test.bat '";"'
# or
.\test.bat "`";`""
Or as #mklement0 pointed out, the stop-parsing symbol --% would also be an option:
.\test.bat --% ";"
Note that this is specific to Powershell syntax.
In CMD, this will suffice:
test.bat ";"

Running executable with command line parameters in PowerShell

This has to be possible. I am able to open a command prompt in windows and do the following:
<some exe> <some exe command line parameters>
There must be an equivalent way to do this in PowerShell or even a standard windows batch file. For example, from the windows command prompt I can start a docker container with:
docker run –-net=kafka -d –-name=zookeeper -e ZOOKEEPER_CLIENT_PORT=2181 confluentinc/cp-zookeeper:4.1.0
however if I try something like this with PowerShell
& "docker" run –-net=kafka -d –-name=zookeeper -e ZOOKEEPER_CLIENT_PORT=2181 confluentinc/cp-zookeeper:4.1.0
it fails with an generic error:
invalid reference format.
Perhaps PowerShell is not suited for this type of advanced use case. Any suggestions would be appreciated!
Is there a better scripting language for advanced usages like this?
I think Start-Process cmdlet will be useful. ArgumentList can be single or double quoted.
Start-Process docker -ArgumentList "run –-net=kafka -d –-name=zookeeper -e ZOOKEEPER_CLIENT_PORT=2181 confluentinc/cp-zookeeper:4.1.0"
By and large, external programs in PowerShell are called the same way as from cmd.exe - there are differences, due to PowerShell having additional metacharacters such as $ and #, but they do not come into play in your specific case.
(Your & "docker" ... variant would work in principle too, but the use of & is only necessary if you must use a quoted or variable-based command name or path).
The problem is that your original command line contains two instances of – (EN DASH, U+2013) instead of the expected ASCII-range - dash (hyphen), which docker doesn't recognize.
A quick way to discover the problem:
# Print the code points of characters outside the ASCII range.
PS> [int[]] [char[]] '& "docker" run –-net=kafka -d –-name=zookeeper -e ZOOKEEPER_CLIENT_PORT=2181 confluentinc/cp-zookeeper:4.1.0' -gt 127
8211
8211
Decimal 8211 is hex. 0x2013, the code point of en-dash, whereas the code point of the regular - is 45 (0x2d).
All that is needed is to replace these – instances with - (and, since docker needn't be quoted, there is no need for &):
docker run --net=kafka -d --name=zookeeper -e ZOOKEEPER_CLIENT_PORT=2181 confluentinc/cp-zookeeper:4.1.0
Your own answer shows a variable-based implementation of the command that is effectively the same as the command above - if all the arguments are known in advance, there is never a need to use variables.
If you do want to use variables, it is much simpler to use a single array variable for all the arguments and pass that:
$dockerExe = 'docker'
$dockerArgs = 'run',
'--net=kafka',
'-d',
'--name=zookeeper',
'-e',
'ZOOKEEPER_CLIENT_PORT=2181',
'confluentinc/cp-zookeeper:4.1.0'
& $dockerExe $dockerArgs
Note:
The executable name/path must always be specified separately, and if it is quoted or involves variable references (as in this case), &, the call operator must be used for invocation, for syntactic reasons.
Passing the arguments as an array this way works with external programs; for PowerShell commands, you'd create a hashtable variable that you pass with sigil # instead of $, a feature known as splatting.
There is a lot of complexities in powershell escaping. I wrote this module to assist with running of external commands:
https://github.com/choovick/ps-invoke-externalcommand
Demo:
https://terminalizer.com/view/49acb54a979
Install-Module -Name ExternalCommand -Scope CurrentUser
Invoke-ExternalCommand -Command "docker" -Arguments #("run","-d","--name=zookeeper","--net=kafka","-e","ZOOKEEPER_CLIENT_PORT=2181", "confluentinc/cp-zookeeper:4.1.0")
Here's how to do it.
$app = 'docker'
$a1 = 'run'
$a2 = '--net=kafka'
$a3 = '-d'
$a4 = '--name=zookeeper'
$a5 = '-e'
$a6 = 'ZOOKEEPER_CLIENT_PORT=2181'
$a7 = 'confluentinc/cp-zookeeper:4.1.0'
& $app $a1 $a2 $a3 $a4 $a5 $a6 $a7

tcl exec to open a program with agruments

I want to open a text file in notepad++ in a particular line number. If I do this in cmdline the command should be:
start notepad++ "F:\Path\test.txt" -n100
And it is working fine from command line. Now I have to do this from tcl. But I can't make this command work with exec. When I try to execute this:
exec "start notepad++ \"F:\Path\test.txt\" -n100"
I am getting this error:
couldn't execute "start notepad++ "F:\Path\test.txt" -n100": no such file or directory.
What am I missing. Please guide.
Similar to this question:
exec {*}[auto_execok start] notepad++ F:/Path/test.txt -n10
First, you need to supply each argument of the command as separate values, instead of a single string/list. Next, to mimic the start command, you would need to use {*}[auto_execok start].
I also used forward slashes instead of backslashes, since you would get a first level substitution and get F:Path est.txt.
EDIT: It escaped me that you could keep the backslashes if you used braces to prevent substitution:
exec {*}[auto_execok start] notepad++ {F:\Path\test.txt} -n10
You can simply surround the entire exec statement in curly braces. Like this:
catch {exec start notepad++.exe f:\Path\test.txt -n10}
I haven't found a perfect solution to this yet. All my execs seem to be different from each other. On windows there are various issues.
Preserving double quotes around filename (or other) arguments.
e.g. in tasklist /fi "pid eq 2060" /nh the quotes are required.
Preserving spaces in filename arguments.
Preserving backslash characters in filename arguments.
[Internally, Windows doesn't care whether pathnames have / or \, but some programs will parse the filename arguments and expect the backslash character].
The following will handle the backslashes and preserve spaces, but will not handle double-quoted arguments. This method is easy to use. You can build up the command line using list and lappend.
set cmd [list notepad]
set fn "C:\\test 1.txt"
lappend cmd $fn
exec {*}$cmd
Using a string variable rather than a list allows preservation of quoted arguments:
set cmd [auto_execok start]
append cmd " notepad"
append cmd " \"C:\\test 1.txt\""
exec {*}$cmd
Note that if you need to supply the full path to the command to be executed, it often needs to be quoted also due to spaces in the pathname:
set cmd "\"C:\\Program Files\\mystuff\\my stuff.exe\" "

Single quotes and double quotes in perl

I'm a newbie in perl and I'm trying to execute an operating system command from a Perl script.
The operating system command is executed with IPC::Open3, and the command is like
$cmd = "mycommand --add \"condition LIKE '%TEXT%' \"";
This command is supposed to insert the string contained after the "add" into a database.
The problem is that it inserts the record in the database without the single quotes around %TEXT%, like this:
condition LIKE %TEXT%
If I execute the command at a command prompt it inserts the string correctly.
How should I format the double and single quotes so that it is inserted in the database correctly?
Thanks for the help
By putting the command in a single string, you are inflicting upon it a pass through your system's shell. (You don't mention if it's cmd.exe or bash or other fun stuff.)
Suggestions:
Creating your system command as an array of strings will avoid the shell re-interpolating your command line.
#cmd = ('mycommand', '--add', q(condition LIKE '%TEXT%'));
Throw in extra backslashes to protect the single quotes from your shell. Prepending echo to your command could help with the debugging....
(my personal favorite) Don't shell out for your Database access. use DBI.
$cmd = q{mycommand --add "condition LIKE '%TEXT%'"};
qq is for double-quoting , q is for single quoting.
This way it takes whole command as it is.