In bash, I often did things like:
[ -z "${someEnvVar}" ] && someEnvVar="default value"
Instead of creating a full-fledge if statement:
if [ -z "${someEnvVar}" ]; then someEnvVar="default value"; fi
Is there a similar shorthand in PowerShell without creating a function or alias?
I'd rather not write the following in PowerShell:
if ("${someVar}" -eq "") {
$someVar = "default value"
}
If seems more readable to me, but you can use short circuit of OR (similar to BASH version):
$someVar = ""
[void]($someVar -ne "" -or ($someVar = "Default"))
$someVar #yields "Default"
$someVar = "Custom"
[void]($someVar -ne "" -or ($someVar = "Default"))
$someVar #yields "Custom"
I am afraid Powershell doesnt have such an elegant solution as C# have:
Exp1 ? Exp2: Exp3
msg = (age >= 18) ? "Welcome" : "Sorry";
But you could do something like this:
$someVar = if ("${someVar}" -eq "") { "default value" }
Here are some options:
$emptystring = ""
if ($emptystring -eq "") {
"The string was empty"
}
if (!$emptystring) {
"The string was null or empty"
}
if (!$someVar) {
"The value was null or unassigned"
}
if ([string]::IsNullOrEmpty($emptystring)) {
"The value was null or empty"
}
This produces the following output:
The string was empty
The string was null or empty
The value was null or unassigned
The value was null or empty
Since PowerShell 7, you can use C#-like If ? Then : Else syntax:
$Variable = -not $Variable ? $Default : $Variable
For pipes, you can now use bash-like || and &&:
(Get-Value && Use-Value) || Set-Value $Default
or
(Get-Value && Use-Value) || (& {Set-Value $Default; Get-Value} | Use-Value)
where & executes a script block (I don't know of a way to chain two commands in a pipe without passing values between them)
Related
Objective: Trying to check if resource exist on azure with bash script
Code that I use :
status=$(az group list --query "[?name.contains(#,'test')]")
if [[ "$status" == null ]];
then
echo "not exist"
else
echo "exist"
fi
I have this resource in azure i.e it should return as "exist" however its says not exist
If I change to a non existant resource group name then time also its gives not exist.
do you see any syntax issue here ?
Instead of script if I execute at commmand line to check , below are results
user#ablab:~$ status=$(az group list --query "[?name.contains(#,'abcd')]")
user#ablab:~$ echo $status
[]
user#ablab:~$ status=$(az group list --query "[?name.contains(#,'test')]")
user#ablab:~$ echo $status
[ { "id": "/subscriptions/xxxx-xxxx-xxx--xxxxx3/resourceGroups/test1", "location": "westeurope", "managedBy": null, "name": "test1", "properties": { "provisioningState": "Succeeded" }, "tags": null, "type": "Microsoft.Resources/resourceGroups" } ]
Now I wanna use if condition, so that if its exist it should process set of flow else set of code..
Please let me know what wrong with my if statement.
The syntax is fine. However, I don't see from your example, that az would write the string null to stdout. In the first case, it prints, according to what you posted, the string []. To catch this case, you would have to test
if [[ $status == '[]' ]]
then
...
The quotes around the string tell bash to not interpret it as a glob pattern.
I want to run determined executable depending on a variable of my script cur_num
I have this code, where the paths are "random", meaning they have nothing to do with the secuential cur_num:
run() {
if [ $cur_num = "0" ]
then ~/pathDirName0/execFile0
elif [ $cur_num = "1" ]
then ~/pathDirName1/execFile1
elif [ $cur_num = "2" ]
then ~/pathDirName2/execFile2
elif [ $cur_num = "3" ]
then ~/pathDirName3/execFile3
elif [ $cur_num = "4" ]
then ~/pathDirName4/execFile4
fi
}
In case there are lots more of cases, that resulys in a very long if - elif statement.
As there are no enums in bash to build the cur_num- path relation, is there a cleaner way to obtain the desired path dynamically instead of with lots of ifs?
Try case
case $cur_num in
0) ~/pathDirName0/execFile0;;
1) ~/pathDirName1/execFile1;;
2) ~/pathDirName2/execFile2;;
...
esac
set allows you to create arrays in a portable manner, so you can use that to create an ordered array:
set -- ~/pathDirName0/execFile0 ~/pathDirName1/execFile1 ~/pathDirName2/execFile2 ~/pathDirName3/execFile3 ~/pathDirName4/execFile4
Then to access these items via an index you do $x where x is the index of the item.
Now your code would look something like this:
run() {
original="$#"
: $((cur_num+=1)) # Positional param indexing starts from 1
set -- ~/pathDirName0/execFile0 ~/pathDirName1/execFile1 \
~/pathDirName2/execFile2 ~/pathDirName3/execFile3 \
~/pathDirName4/execFile4
eval "$"$cur_num"" # cur_num = 1, accesses $1 == execFile0
set -- $original # Restore original args
}
A method that is both less and more hacky that would work for indexes above 9 and not mess with the original positional parameters nor use eval
run() {
count=0
for item in "$#"; do
[ "$count" = "$cur_num" ] && {
"$item"
return
}
: "$((count+=1))"
done
echo "No item found at index '$cur_num'"
}
cur_num="$1" # Assuming first arg of script.
run ~/pathDirName0/execFile0 ~/pathDirName1/execFile1 \
~/pathDirName2/execFile2 ~/pathDirName3/execFile3 \
~/pathDirName4/execFile4
I have removed parentheses, but still I was not able to fetch ENV_NODE value in second function scpTAR. Please let me know what is wrong.
set -x
MASTER_HOSTNAME=`hostname | cut -d . -f1`
TARGET_ENVIRONMENT = it
evaluateEnvProp(){
if [ ${TARGET_ENVIRONMENT} = it ]; then
ENV_NAME=it && ENV_NODE=1cf62108e084
fi
}
scpTAR() {
echo ENV_NODE
echo ${ENV_NODE}
if [ ENV_NODE = ${MASTER_HOSTNAME} ] ; then
echo "scpTAR ENV_NODE = ${MASTER_HOSTNAME} "
else
"echo 'scpTAR ssh other node than jenkins server ENV_NODE=${MASTER_HOSTNAME}'"
fi
}
main(){
scpTAR
}
main
As #cyrus said, variables are global by default. What you did is setting the variables in a subshell:
( ENV_NAME=it && ENV_NODE=xyx && ENV_WLS_DOMAIN=user1 && ENV_NODE_PATH=path )
Because of that, these are gone (not propagated to calling script's environment) once the subshell exists. This is why you do not see their values set in scpTAR function. Remove the parentheses and your code should start working.
Update
Updated version of your code (based on answer by itChi) has another major error. You put spaces around the assignment operator when setting TARGET_ENVIRONMENT = it. This syntax is invalid and as a result TARGET_ENVIRONMENT is not assigned the specified value, thus the condition inside evaluateEnvProp function evaluates to false and ENV_NODE variable is not being set. Removing the spaces should solve the problem. You also did not call evaluateEnvProp as pointed out in update to #itChi's answer.
I'd highly recommend that you start using ShellCheck to verify correctness of your scripts.
As mentioned, variables are global by default, so if you reference them in your scpTAR function, you will get a return value.
However, as a second method, should you wish, you can reference it like so:
scpTAR $ENV_NAME $ENV_NODE $ENV_WLS_DOMAIN $ENV_NODE_PATH
Then in your scpTAR function reference them as:
echo "$1 $2 $3 $4"
it xyx user1 path
Particularly useful should you wish to run code on another machine, run a remote script, or set your script up to pass variables as arguments from bash.
EDIT:
Sorry if I tread on someone's toes, but here is your answer without subshell:
evalEnvProp(){
if [ ${TARGET_ENVIRONMENT} = "it" ]; then
ENV_NAME=it
ENV_NODE=xyx
ENV_WLS_DOMAIN=user1
ENV_NODE_PATH=path
fi
}
scpTAR() {
echo $ENV_NODE_PATH
}
main(){
evalEnvProp
scpTAR
}
main
Update2:
#!/bin/bash
set -x
MASTER_HOSTNAME=`hostname | cut -d . -f1`
TARGET_ENVIRONMENT=it
evaluateEnvProp(){
if [ ${TARGET_ENVIRONMENT} = "it" ]; then
ENV_NAME=it && ENV_NODE=1cf62108e084
fi
}
scpTAR() {
echo ENV_NODE
echo ${ENV_NODE}
if [ ENV_NODE == ${MASTER_HOSTNAME} ] ; then
echo "scpTAR ENV_NODE = ${MASTER_HOSTNAME} " else
"echo 'scpTAR ssh other node than jenkins server >ENV_NODE=${MASTER_HOSTNAME}'"
fi
}
main(){
evaluateEnvProp
scpTAR
}
main
MASTER_HOSTNAME needs `` for the command. you are calling hostname. You can also accomplish this with $()
Spacing in the IF statement either side of the = sign. otherwise you are not evaluating, you are setting a variable.
In your edit you missed out a function call to evaluateEnvProp which failed because you didn't set the variable.
I'm using FPM tool to create .deb package. This tool create before/after remove package from supported files.
Unfortunatly the bash script generated by FPM contains such function
dummy() {
}
And this script exit with an error:
Syntax error: "}" unexpected
Does BASH doesn't allow empty functions? Which version of bash/linux have this limitation?
You could use : that is equivalent to true and is mostly used
as do nothing operator...
dummy(){
:
}
A one liner
dummy(){ :; }
: is the null command
; is needed in the one line format
An empty bash function may be illegal. function contains only comments will be considered to be empty too.
a ":" (null command) can be placed in function if you want to "DO NOTHING"
see: http://tldp.org/LDP/abs/html/functions.html
I recommend this one:
dummy(){ unused(){ :;} }
If you use : null command, it will be printed by xtrace option:
(
set -o xtrace
dummy(){ :; }
dummy "null command"
)
echo ------
(
set -o xtrace
dummy(){ unused(){ :;} }
dummy "unused function"
)
output:
+ dummy 'null command'
+ :
------
+ dummy 'unused function'
For debug I use wrapper like this:
main() {(
pwd # doing something in subshell
)}
print_and_run() {
clear
(
eval "$1() { unused() { :; } }"
set -o xtrace
"$#"
)
time "$#"
}
print_and_run main aaa "bb bb" ccc "ddd"
# output:
# + main aaa 'bb bb' ccc ddd
# ..
dummy_success(){ true; } #always returns 0
dummy_fail(){ false; } #always returns 1
minimal functions returning always OK or ERROR status..
also useful to redefine missing functions with empty function
(or update it with some default action, for example - debug warning):
#!/bin/sh
#avoid error if calling unimportant_func which is underfined
declare -F unimportant_func >/dev/null || unimportant_func() { true; }
#get error if calling important_func which is underfined
declare -F important_func >/dev/null || important_func() { false; }
# print debug assert if function not exists
declare -F some_func >/dev/null || some_func() {
echo "assert: '${FUNCNAME[0]}() is not defined. called from ${BASH_SOURCE[1]##*/}[${BASH_LINENO[0]}]:${FUNCNAME[1]}($#)" 1>&2; }
my_func(){
echo $(some_func a1 a2 a3)
important_func && echo "important_func ok" || echo "important_func error"
unimportant_func && echo "unimportant_func ok" || echo "unimportant_func error"
}
my_func
output:
$> testlib.sh
assert: 'some_func() is not defined. called from testlib.sh[15]:my_func(a1 a2 a3)
important_func error
unimportant_func ok
tclsh is a shell containing the TCL commands.
The TCL uplevel command evaluates the given TCL script, but it fails to evaluate a tclsh script (which can contain bash commands).
How can I obtain an analogue of uplevel for the tclsh script?
Consider this TCL script:
# file main.tcl
proc prompt { } \
{
puts -nonewline stdout "MyShell > "
flush stdout
}
proc process { } \
{
catch { uplevel #0 [gets stdin] } got
if { $got ne "" } {
puts stderr $got
flush stderr
}
prompt
}
fileevent stdin readable process
prompt
while { true } { update; after 100 }
This is a kind of TCL shell, so when you type tclsh main.tcl it shows a prompt MyShell > and it acts like you are in interactive tclsh session. However, you are in non-interactive tclsh session, and everything you type is evaluated by the uplevel command. So here you can't use bash commands like you can do it int interactive tclsh session. E.g. you can't open vim right from the shell, also exec vim will not work.
What I want is to make MyShell > act like interactive tclsh session. The reason why I can't just use tclsh is the loop at the last line of main.tcl: I have to have that loop and everything has to happen in that loop. I also have to do some stuff at each iteration of that loop, so can use vwait.
Here is the solution.
I have found no better solution then to overwrite the ::unknown function.
# file main.tcl
proc ::unknown { args } \
{
variable ::tcl::UnknownPending
global auto_noexec auto_noload env tcl_interactive
global myshell_evaluation
if { [info exists myshell_evaluation] && $myshell_evaluation } {
set level #0
} else {
set level 1
}
# If the command word has the form "namespace inscope ns cmd"
# then concatenate its arguments onto the end and evaluate it.
set cmd [lindex $args 0]
if {[regexp "^:*namespace\[ \t\n\]+inscope" $cmd] && [llength $cmd] == 4} {
#return -code error "You need an {*}"
set arglist [lrange $args 1 end]
set ret [catch {uplevel $level ::$cmd $arglist} result opts]
dict unset opts -errorinfo
dict incr opts -level
return -options $opts $result
}
catch {set savedErrorInfo $::errorInfo}
catch {set savedErrorCode $::errorCode}
set name $cmd
if {![info exists auto_noload]} {
#
# Make sure we're not trying to load the same proc twice.
#
if {[info exists UnknownPending($name)]} {
return -code error "self-referential recursion in \"unknown\" for command \"$name\"";
}
set UnknownPending($name) pending;
set ret [catch {
auto_load $name [uplevel $level {::namespace current}]
} msg opts]
unset UnknownPending($name);
if {$ret != 0} {
dict append opts -errorinfo "\n (autoloading \"$name\")"
return -options $opts $msg
}
if {![array size UnknownPending]} {
unset UnknownPending
}
if {$msg} {
if {[info exists savedErrorCode]} {
set ::errorCode $savedErrorCode
} else {
unset -nocomplain ::errorCode
}
if {[info exists savedErrorInfo]} {
set ::errorInfo $savedErrorInfo
} else {
unset -nocomplain ::errorInfo
}
set code [catch {uplevel $level $args} msg opts]
if {$code == 1} {
#
# Compute stack trace contribution from the [uplevel].
# Note the dependence on how Tcl_AddErrorInfo, etc.
# construct the stack trace.
#
set errorInfo [dict get $opts -errorinfo]
set errorCode [dict get $opts -errorcode]
set cinfo $args
if {[string bytelength $cinfo] > 150} {
set cinfo [string range $cinfo 0 150]
while {[string bytelength $cinfo] > 150} {
set cinfo [string range $cinfo 0 end-1]
}
append cinfo ...
}
append cinfo "\"\n (\"uplevel\" body line 1)"
append cinfo "\n invoked from within"
append cinfo "\n\"uplevel $level \$args\""
#
# Try each possible form of the stack trace
# and trim the extra contribution from the matching case
#
set expect "$msg\n while executing\n\"$cinfo"
if {$errorInfo eq $expect} {
#
# The stack has only the eval from the expanded command
# Do not generate any stack trace here.
#
dict unset opts -errorinfo
dict incr opts -level
return -options $opts $msg
}
#
# Stack trace is nested, trim off just the contribution
# from the extra "eval" of $args due to the "catch" above.
#
set expect "\n invoked from within\n\"$cinfo"
set exlen [string length $expect]
set eilen [string length $errorInfo]
set i [expr {$eilen - $exlen - 1}]
set einfo [string range $errorInfo 0 $i]
#
# For now verify that $errorInfo consists of what we are about
# to return plus what we expected to trim off.
#
if {$errorInfo ne "$einfo$expect"} {
error "Tcl bug: unexpected stack trace in \"unknown\"" {} [list CORE UNKNOWN BADTRACE $einfo $expect $errorInfo]
}
return -code error -errorcode $errorCode -errorinfo $einfo $msg
} else {
dict incr opts -level
return -options $opts $msg
}
}
}
if { ( [info exists myshell_evaluation] && $myshell_evaluation ) || (([info level] == 1) && ([info script] eq "") && [info exists tcl_interactive] && $tcl_interactive) } {
if {![info exists auto_noexec]} {
set new [auto_execok $name]
if {$new ne ""} {
set redir ""
if {[namespace which -command console] eq ""} {
set redir ">&#stdout <#stdin"
}
uplevel $level [list ::catch [concat exec $redir $new [lrange $args 1 end]] ::tcl::UnknownResult ::tcl::UnknownOptions]
dict incr ::tcl::UnknownOptions -level
return -options $::tcl::UnknownOptions $::tcl::UnknownResult
}
}
if {$name eq "!!"} {
set newcmd [history event]
} elseif {[regexp {^!(.+)$} $name -> event]} {
set newcmd [history event $event]
} elseif {[regexp {^\^([^^]*)\^([^^]*)\^?$} $name -> old new]} {
set newcmd [history event -1]
catch {regsub -all -- $old $newcmd $new newcmd}
}
if {[info exists newcmd]} {
tclLog $newcmd
history change $newcmd 0
uplevel $level [list ::catch $newcmd ::tcl::UnknownResult ::tcl::UnknownOptions]
dict incr ::tcl::UnknownOptions -level
return -options $::tcl::UnknownOptions $::tcl::UnknownResult
}
set ret [catch {set candidates [info commands $name*]} msg]
if {$name eq "::"} {
set name ""
}
if {$ret != 0} {
dict append opts -errorinfo "\n (expanding command prefix \"$name\" in unknown)"
return -options $opts $msg
}
# Filter out bogus matches when $name contained
# a glob-special char [Bug 946952]
if {$name eq ""} {
# Handle empty $name separately due to strangeness
# in [string first] (See RFE 1243354)
set cmds $candidates
} else {
set cmds [list]
foreach x $candidates {
if {[string first $name $x] == 0} {
lappend cmds $x
}
}
}
if {[llength $cmds] == 1} {
uplevel $level [list ::catch [lreplace $args 0 0 [lindex $cmds 0]] ::tcl::UnknownResult ::tcl::UnknownOptions]
dict incr ::tcl::UnknownOptions -level
return -options $::tcl::UnknownOptions $::tcl::UnknownResult
}
if {[llength $cmds]} {
return -code error "ambiguous command name \"$name\": [lsort $cmds]"
}
}
return -code error "invalid command name \"$name\""
}
proc prompt { } \
{
puts -nonewline stdout "MyShell > "
flush stdout
}
proc process { } \
{
global myshell_evaluation
set myshell_evaluation true
catch { uplevel #0 [gets stdin] } got
set myshell_evaluation false
if { $got ne "" } {
puts stderr $got
flush stderr
}
prompt
}
fileevent stdin readable process
prompt
while { true } { update; after 100 }
The idea is to modify the ::unknown function so that it handles MyShell evaluations as the ones of tclsh interactive session.
This is an ugly solution, as I am fixing the code of ::unknown function which can be different for different systems and diferent versions of tcl.
Is there any solution which circumvents these issues?
uplevel does not only evaluate a script, but it evaluates it in the stack context of the caller of the instance where it's executed. It's a pretty advanced command which should be used when you define your own execution control structures, and OFC it's TCL specific - I find myself unable to imagine how a tclsh equivalent should work.
If you just want to evaluate another script, the proper TCL command would be eval. If that other script is tclsh, why don't you just open another tclsh?
The simplest answer, I think, would be to use the approach you're using; to rewrite the unknown command. Specifically, there is a line in it that checks to make sure the current context is
Not run in a script
Interactive
At the top level
If you replace that line:
if {([info level] == 1) && ([info script] eq "") && [info exists tcl_interactive] && $tcl_interactive} {
with something that just checks the level
if ([info level] == 1} {
you should get what you want.
Vaghan, you do have the right solution. Using ::unknown is how tclsh itself provides the interactive-shell-functionality you're talking about (invoking external binaries, etc). And you've lifted that same code and included it in your MyShell.
But, if I understand your concerns about it being an "ugly solution", you'd rather not reset ::unknown ?
In which case, why not just append the additional functionality you want to the end of the pre-existing ::unknown's body (or prepend it - you choose)
If you search on the Tcl'ers wiki for "let unknown know", you'd see a simple proc which demonstrates this. It prepends new code to the existing ::unknown, so you can keep adding additional "fallback code" as you go along.
(apologies if I've misunderstood why you feel your solution is "ugly")
Instead of changing the unknown proc, I suggest that you make the changes to evaluate the expresion
if {([info level] == 1) && ([info script] eq "") && [info exists tcl_interactive] && $tcl_interactive} {
to true.
info level: call your stuff with uplevel #0 $code
info script: call info script {} to set it to an empty value
tcl_interactive. Simple: set ::tcl_interactive 1
so your code would be
proc prompt { } {
puts -nonewline stdout "MyShell > "
flush stdout
}
proc process { } {
catch { uplevel #0 [gets stdin] } got
if { $got ne "" } {
puts stderr $got
flush stderr
}
prompt
}
fileevent stdin readable process
set tcl_interactive 1
info script {}
prompt
vwait forever