Autohotkey / AHK - Param %1% not accessible within a function - command-line

I'm having a problem with accessing the %1% ( Startup Param that has been passed to the Script by the console ) in Autohotkey.
When I use the following code (outside of a function):
Msgbox %1%
I get the output of the Param that has been passed to the Script. But as soon as I use the following Code:
HelloWorld() {
Msgbox %1%
}
HelloWorld()
The output is empty.
I also tried to assign %1% to a global variable, or to pass it to the Function as a parameter but it didn't work for me neither.
Thank you

I believe the command line parameter variables are considered global variables, so in order to use them in a non-expression context inside a function you have to declare them as global:
HelloWorld() {
global 1
Msgbox %1%
}
HelloWorld()
It gets even more confusing once you want to use them in expressions (such as using % in the text argument for MsgBox), since they will be treated as numbers so you have to indirectly access them through variables:
HelloWorld() {
;global 1
; Neither of these two expressions access the variable named "1"
;Msgbox % 1
;Msgbox % %1%
; You have to do this instead:
p := 1
MsgBox % %p% ; p is translated to 1 and then "1" is used as a variable name
}
HelloWorld()
Note that doing this doesn't require global 1.
If you're using the newest version of AHK, you instead probably want to use the newly introduced built-in variable A_Args, which is an array that holds the command line parameters. Being built-in, it doesn't have to be declared global, and it ultimately makes the code clearer:
HelloWorld() {
MsgBox % "Number of command line args received: " A_Args.Length() "`n"
. "First argument: " A_Args[1]
}
HelloWorld()

Just declare your cli variables as Global - outside the function - to make them globally available to any and all internal functions. For me, this is how I do it with my version of AHK (Version 1.1.25.01):
Global 1, 2, 3
HelloWorld() {
MsgBox Hello`t1:`t%1%`n`t2:`t%2%`n`t3:`t%3%
}
HelloWorld()
Note, these are different command lines:
"Scripts\myScript.ahk" one two three
"Scripts\myScript.ahk" "one two" three
"Scripts\myScript.ahk" "one" "two three"
"Scripts\myScript.ahk" "one two three"
The first is three separate parameters, the second and third, only two and the last is only one param (2 and 3 exist, but are empty).
Hth,

Related

How to substitute repeated script statements in multiple functions by a predefined script block or other mean?

I have multiple functions. By the end of the function they all need to go through exactly the same couple of lines of code, doing some logistic work. How can I define these couple of lines of code into a script block outside these functions and be able to use for all functions, either global reference or passed in as an argument(less preferred)? These lines of code will involve both global and local variables. The ideal is whenever I need to update the logistic work content, I can update it in one place, like script block definition, instead of using a function, which seems overkill for a few lines of codes. Thanks.
function A {
...
$var1 = $global:x + 1
Write-Host var1 value is $var1
}
function B {
...
$var1 = $global:x + 1
Write-Host var1 value is $var1
}
$global:x = 0
A
B

SAS IML use of Mattrib with Macro (symget) in a loop

In an IML proc I have several martices and several vectors with the names of columns:
proc IML;
mydata1 = {1 2 3, 2 3 4};
mydata2 = {1 2, 2 3};
names1 = {'red' 'green' 'blue'};
names2 = {'black' 'white'};
To assign column names to columns in matrices one can copypaste the mattrib statement enough times:
/* mattrib mydata1 colname=names1;*/
/* mattrib mydata2 colname=names2;*/
However, in my case the number of matrices is defined at execution, thus a do loop is needed. The following code
varNumb=2;
do idx=1 to varNumb;
call symputx ('mydataX', cat('mydata',idx));
call symputx ('namesX', cat('names',idx));
mattrib (symget('mydataX')) colname=(symget('namesX'));
end;
print (mydata1[,'red']) (mydata2[,'white']);
quit;
however produces the "Expecting a name" error on the first symget.
Similar question Loop over names in SAS-IML? offers the macro workaround with symget, what produces an error here.
What is the correct way of using mattrib with symget? Is there other way of making a variable from a string than macro?
Any help would be appreciated.
Thanks,
Alex
EDIT1
The problem is in the symget function. The &-sign resolves the name of the matrix contained in the macro variable, the symget only returns the name of the macro.
proc IML;
mydata1 = {1 2 3};
call symputx ('mydataX', 'mydata1');
mydataNew = (symget('mydataX'));
print (&mydataX);
print (symget("mydataX"));
print mydataNew;
quit;
results in
mydata1 :
1 2 3
mydata1
mydataNew :
mydata1
Any ideas?
EDIT2
Function value solves the symget problem in EDIT1
mydataNew = value(symget('mydataX'));
print (&mydataX);
print (value(symget("mydataX")));
print mydataNew;
The mattrib issue but persists.
SOLVED
Thanks Rick, you have opened my eyes to CALL EXECUTE() statement.
When you use CALL SYMPUTX, you should not use quotes for the second argument. Your statement
call symputx ('mydataX', 'mydata1');
assigns the string 'mydata1' to the macro variable.
In general, trying to use macro variables in SAS/IML loops often results in complicated code. See the article Macros and loops in the SAS/IML language for an indication of the issues caused by trying to combine a macro preprocessor with an interactive language. Because the MATTRIB statement expects a literal value for the matrix name, I recomend that you use CALL EXECUTE rather than macro substitution to execute the MATTRIB statement.
You are also having problems because a macro variable is always a scalar string, whereas the column name is a vector of strings. Use the ROWCAT function to concatenate the vector of names into a single string.
The following statements accomplish your objective without using macro variables:
/* Use CALL EXECUTE to set matrix attributes dynamically.
Requires that matrixName and varNames be defined at main scope */
start SetMattrib;
cmd = "mattrib " + matrixName + " colname={" + varNames + "};";
*print cmd; /* for debugging */
call execute(cmd);
finish;
varNumb=2;
do idx=1 to varNumb;
matrixName = cat('mydata',idx);
varNames = rowcat( value(cat('names',idx)) + " " );
run SetMattrib;
end;

Global variable does not have global scope

supposedlyGlobalVariable := "blah"
ARoutine()
{
localVariable := "asdf"
MsgBox, The global variable value is %supposedlyGlobalVariable%. The local variable value is %localVariable%.
}
^!X:: ;This assigns the hotkey CTRL + ALT + X to run the routine
ARoutine()
return
Run the code and the result is:
"The global variable value is . The local variable value is asdf."
The documentation states:
Variable scope and declarations: With the exception of local variables
in functions, all variables are global; that is, their contents may be
read or altered by any part of the script.
Why does my global variable not have scope within the function?
The documentation for global variables can be found here:
https://autohotkey.com/docs/Functions.htm#Global
Global variables
To refer to an existing global variable inside a function (or create a
new one), declare the variable as global prior to using it. For
example:
LogToFile(TextToLog)
{
global LogFileName
FileAppend, %TextToLog%`n, %LogFileName%
}
I believe the concept of global, with AHK, is a bit different than in other languages. With AHK you can create a variable and use it within multiple hotkeys, and subroutines, without declaring it as global.
Gv := 0
f1::SetTimer, Action, % (on:=!on) ? (1000) : ("Off")
Action:
Gv++
trayTip,, % Gv
Return
f2::Msgbox, % Gv
Explaination of code:
The F1 key toggles a timer to run the subroutine: Action every 1000ms.
% starts an expression.
on:=!on reverses the binary value of variable on every time F1 is pressed.
?: together is called the ternary operator.
When on=1 delay is set to 1000ms; when on=0 the timer is turned Off.
The ++ operator adds 1 to variable Gv.
This makes things easier:
https://www.autohotkey.com/docs/Functions.htm#SuperGlobal
Super-global variables [v1.1.05+]: If a global declaration appears
outside of any function, it takes effect for all functions by default
(excluding force-local functions). This avoids the need to redeclare
the variable in each function. However, if a function parameter or
local variable with the same name is declared, it takes precedence
over the global variable. Variables created by the class keyword are
also super-global.
Just declare your variable as global in the main script:
global supposedlyGlobalVariable := "blah"
P.Brian, It works when you do this.. I know it doesn't explain why, but this might be your workaround.
#Persistent
GlobalVariable = "blah"
RETURN
ARoutine:
{
localVariable := "asdf"
MsgBox, The global variable value is %GlobalVariable%. The local variable value is %localVariable%.
}
Return
^!X:: ;This assigns the hotkey CTRL + ALT + X to run the routine
gosub, ARoutine
return
You just need to declare the variable as global inside your function
supposedlyGlobalVariable := "blah"
ARoutine()
{
global supposedlyGlobalVariable
localVariable := "asdf"
MsgBox, The global variable value is %supposedlyGlobalVariable%. The local variable
value is %localVariable%.
}
^!X:: ;This assigns the hotkey CTRL + ALT + X to run the routine
ARoutine()
return

How to modify parent scope variable using Powershell

I'm fairly new to powershell, and I'm just not getting how to modify a variable in a parent scope:
$val = 0
function foo()
{
$val = 10
}
foo
write "The number is: $val"
When I run it I get:
The number is: 0
I would like it to be 10. But powershell is creating a new variable that hides the one in the parent scope.
I've tried these, with no success (as per the documentation):
$script:$val = 10
$global:$val = 10
$script:$val = 10
But these don't even 'compile' so to speak.
What am I missing?
You don't need to use the global scope. A variable with the same name could have been already exist in the shell console and you may update it instead. Use the script scope modifier. When using a scope modifier you don't include the $ sign in the variable name.
$script:val=10
The parent scope can actually be modified directly with Set-Variable -Scope 1 without the need for Script or Global scope usage. Example:
$val = 0
function foo {
Set-Variable -scope 1 -Name "Val" -Value "10"
}
foo
write "The number is: $val"
Returns:
The number is: 10
More information can be found in the Microsoft Docs article About Scopes. The critical excerpt from that doc:
Note: For the cmdlets that use the Scope parameter, you can also refer to scopes by number. The number describes the relative position of one scope to another. Scope 0 represents the current, or local, scope. Scope 1 indicates the immediate parent scope. Scope 2 indicates the parent of the parent scope, and so on. Numbered scopes are useful if you have created many recursive scopes.
Be aware that recursive functions require the scope to be adjusted accordingly:
$val = ,0
function foo {
$b = $val.Count
Set-Variable -Name 'val' -Value ($val + ,$b) -Scope $b
if ($b -lt 10) {
foo
}
}
Let me point out a third alternative, even though the answer has already been made. If you want to change a variable, don't be afraid to pass it by reference and work with it that way.
$val=1
function bar ($lcl)
{
write "In bar(), `$lcl.Value starts as $($lcl.Value)"
$lcl.Value += 9
write "In bar(), `$lcl.Value ends as $($lcl.Value)"
}
$val
bar([REF]$val)
$val
That returns:
1
In bar(), $lcl.Value starts as 1
In bar(), $lcl.Value ends as 10
10
If you want to use this then you could do something like this:
$global:val=0
function foo()
{
$global:val=10
}
foo
write "The number is: $val"
Perhaps the easiest way is to dot source the function:
$val = 0
function foo()
{
$val = 10
}
. foo
write "The number is: $val"
The difference here being that you call foo via . foo.
Dot sourcing runs the function as normal, but it runs inside the parent scope, so there is no child scope. This basically removes scoping. It's only an issue if you start setting or overwriting variables/definitions in the parent scope unintentionally. For small scripts this isn't usually the case, which makes dot sourcing really easy to work with.
If you only want a single variable, then you can use a return value, e.g.,
$val = 0
function foo()
{
return 10
}
$val = foo
write "The number is: $val"
(Or without the return, as it's not necessary in a function)
You can also return multiple values to set multiple variables in this manner, e.g., $a, $b, $c = foo if foo returned 1,2,3.
The above approaches are my preferred way to handle cross-scope variables.
As a last alternative, you can also place the write in the function itself, so it's writing the variable in the same scope as it's being defined.
Of course, you can also use the scope namespace solutions, Set-Variable by scope, or pass it via [ref] as demonstrated by others here. There are many solutions in PowerShell.

Declare global variables in a loop in MATLAB

Is it possible to declare global variables in MATLAB inside a loop:
cellvar = { 'ni' ; 'equity' ; 'assets' } ;
for i = 1:size(cellvar,1)
global cellvar{1} % --> THIS GIVES AN ERROR
end
% Desired result:
global ni
global equity
global assets
Matlab documentation says: "There is no function form of the global command (i.e., you cannot use parentheses and quote the variable names)." Any suggested work-around? Thanks!
You can use the EVAL function to do this:
for var = 1:numel(cellvar)
eval(['global ' cellvar{var}]);
end
Also, since GLOBAL accepts a command-line list of variable names, you could avoid the for loop by using SPRINTF to concatenate your variable names into one string to be evaluated:
eval(['global' sprintf(' %s',cellvar{:})]);