I am trying to iterate over a fish shell list and change the values of the elements saving those new values into that list.
The original list is populated like this:
set -l dockApplications (ls $HOME/.config/plank/dock1/launchers/)
This works and produces a list like this:
emacsclient.dockitem firefox.dockitem monodevelop.dockitem Thunar.dockitem
Now I want to iterate over it and change the string "dockitem" to "desktop".
I have tried a for loop but I do not appear to be using it correctly:
for application in $dockApplications
echo $application
set application (string replace 'dockitem' 'desktop' $application )
echo $application
echo "==========="
end
This echos the before and after the string operation and I produce the correct string. but when I do something like echo $dockApplications after the for loop. I get the list of strings with the "dockitem" extension.
I have also tried setting the $dockApplications variable differently like:
set -l dockApplications (string replace 'dockitem' 'desktop' (ls $HOME/.config/plank/$dockdir/launchers/))
But that seems to return the same list of strings with the "dockitem" extension.
I think I am fundamentally misunderstanding either how variables are assigned or how the scope of them is handled here.
I have never fully wrapped my head around shell scripting. I can read it and get what is going on but when it comes to implementing it I hit a few road blocks when I try to achieve something in a way that I would in a different language. But I realize the power of shell scripting and would like to get better at it. So any pointers on how to do this in a fish shell idiomatic way are greatly appreciated.
I would iterate over the indices of the list so you can update in place:
set apps (printf "%s\n" ./launchers/*.dockitem)
for i in (seq (count $apps))
set apps[$i] (string replace "dockitem" "desktop" $apps[$i])
end
printf "%s\n" $apps
This also works without a loop:
set apps (string replace dockitem desktop (printf "%s\n" ./launchers/*.dockitem))
printf "%s\n" $apps
If I recall, fish splits the output of a command substitution on newlines when saving to an array
It may not be the most elegant solution, but here is a pure fish way of doing this using the venerable string function.
set mystring "Lorem ipsum dolor sit amet, consectetur adipisicing elit"
set mywords (string split " " $mystring)
for i in (seq (count $mywords))
set mywords[$i] (string upper (string sub -s 1 -l 1 $mywords[$i]))(string sub -s 2 $mywords[$i])
end
echo $mywords
Related
I have this idea in mind:
I have this number: CN=20
and a list=( "xa1-" "xa2-" "xb1-" "xb2-")
and this is my script:
for a in "${list[#]}"; do
let "CN=$(($CN+1))"
echo $CN
Output:
21
22
23
24
I am trying to create a loop where it creates the following variables, which will be referenced later in my script:
fxp0_$CN="fxp-$a$CN"
fxp0_21="fxp-xa1-21"
fxp0_22="fxp-xa2-22"
fxp0_23="fxp-xb1-23"
fxp0_24="fxp-xb2-24"
However, I have not been able to find a way to change the variable name within my loop. Instead, I was trying myself and I got this error when trying to change the variable name:
scripts/srx_file_check.sh: line 317: fxp0_21=fxp0-xa2-21: command not found
After playing around I found the solution!
for a in "${list[#]}"; do
let "CN=$(($CN+1))"
fxp_int="fxp0-$a$CN"
eval "fxp0_$CN=${fxp_int}"
done
echo $fxp0_21
echo $fxp0_22
echo $fxp0_23
echo $fxp0_24
echo $fxp0_25
echo $fxp0_26
echo $fxp0_27
echo $fxp0_28
Output:
fxp0-xa1-21
fxp0-xa2-22
fxp0-xb1-23
fxp0-xb2-24
fxp0-xc1-25
fxp0-xc2-26
fxp0-xd1-27
fxp0-xd2-28
One common method for maintaining a dynamically generated set of variables is via arrays.
When the variable names vary in spelling an associative array comes in handy whereby the variable 'name' acts as the array index.
In this case since the only thing changing in the variable names is a number we can use a normal (numerically indexed) array, eg:
CN=20
list=("xa1-" "xa2-" "xb1-" "xb2-")
declare -a fxp0=()
for a in "${list[#]}"
do
(( CN++ ))
fxp0[${CN}]="fxp-${a}${CN}"
done
This generates:
$ declare -p fxp0
declare -a fxp0=([21]="fxp-xa1-21" [22]="fxp-xa2-22" [23]="fxp-xb1-23" [24]="fxp-xb2-24")
$ for i in "${!fxp0[#]}"; do echo "fxp0[$i] = ${fxp0[$i]}"; done
fxp0[21] = fxp-xa1-21
fxp0[22] = fxp-xa2-22
fxp0[23] = fxp-xb1-23
fxp0[24] = fxp-xb2-24
As a general rule can I tell you that it's not a good idea to modify names of variables within loops.
There is, however, a way to do something like that, using the source command, as explained in this URL with some examples. It comes down to the fact that you treat a file as a piece of source code.
Good luck
I have a Bash function that takes multiple command-line arguments:
make_pizza() {
echo "Pizza(sauce=\"$1\", crust=\"$2\", cheese=\"$3")"
}
It doesn't matter in this simple example, but in a longer script, naming the variables is good coding practice:
make_pizza() {
sauce="$1"
crust="$2"
cheese="$3"
echo "Pizza(sauce=\"$sauce\", crust=\"$crust\", cheese=\"$cheese")"
}
This makes the rest of the script more readable, and allows easily refactoring the order of the arguments.
It's quicker and (arguably) cleaner to do the variable assignment in one line, like so:
make_pizza() {
read -r sauce crust cheese <<< "$#"
echo "Pizza(sauce=\"$sauce\", crust=\"$crust\", cheese=\"$cheese")"
}
This particular read design makes it even simpler to insert or reorder the arguments later (i.e., there's no renumbering required). Unfortunately, it breaks on arguments with spaces:
$ make_pizza "Grandma's marinara" "Chicago-style" "stilton"
Pizza(sauce="Grandma's", crust="marinara", cheese="Chicago-style stilton")
Is there a compact, one-line idiom like this that works with arguments containing spaces?
(EDIT: This is a toy example to illustrate the problem. The question is about how to compactly parse numbered arguments into named variables, not about code golf with this particular example.)
This is a one-liner:
$ make_pizza() { printf "Pizza(sauce=%s, crust=%s, cheese=%s)\n" "$#"; }
$ make_pizza "Grandma's marinara" "Chicago-style" "stilton"
Pizza(sauce=Grandma's marinara, crust=Chicago-style, cheese=stilton)
If you want to store your variables in one line, you may use the semicolon between each assignment:
make_pizza() {
sauce="$1"; crust="$2"; cheese="$3"
echo "Pizza(sauce=\"$sauce\", crust=\"$crust\", cheese=\"$cheese\")"
}
You can also put everything in a single line, using more semicolons:
make_pizza() { sauce="$1"; crust="$2"; cheese="$3"; echo "Pizza(sauce=\"$sauce\", crust=\"$crust\", cheese=\"$cheese\")"; }
If you simply want to echo but you don't really care about storing the arguments into variables:
make_pizza() { echo "Pizza(sauce=\"$1\", crust=\"$2\", cheese=\"$3\")"; }
An exercise in syntactic sugar -
$: pizza(){ local sauce=0 crust=1 cheese=2 pizza=( "$#" )
echo "sauce is \"${pizza[sauce]}\", cheese is \"${pizza[cheese]}\", crust is \"${pizza[crust]}\"."
}
$: pizza "Sicilian Marinara" "deep dish" "mozzarella & romano"
sauce is "Sicilian Marinara", cheese is "mozzarella & romano", crust is "deep dish".
Arguments are still order dependent this way, though.
How can I assign command line arguments with spaces?
You have the values in positional arguments already - just use them.
sauce="$1"
crust="$2"
cheese="$3"
i have a a couple of variables with a number in its names. e.g
SERVER_IP48_SUBNET
..
SERVER_IP60_SUBNET
And an additional variable
SERVER_IP
Im trying to expand/concatenate them in the following way:
ALLIPs=${SERVER_IP}
for i in {48..64}; do
ALLIPs=${ALLIPs},${SERVER_IP${i}_SUBNET}
done
as you can imagine this script fails saying:
Wrong substitution
Does anybody of you know a good solution for this problem?
Thanks so far
Use a nameref with bash version 4.3 +
ALLIPs=${SERVER_IP}
for i in {48..64}; do
declare -n tmp="SERVER_IP${i}_SUBNET"
ALLIPs+=",$tmp"
done
But you should really be using an array in the first place:
server_ip=0.0.0.0
subnet_ip=(
[48]=1.1.1.1
[49]=2.2.2.2
# ...
[64]=16.16.16.16
)
all_ips=( "$server_ip" )
for i in {48..64}; do
all_ips+=( "${subnet_ip[i]}" )
done
(
IFS=,
echo "ALLIPs = ${all_ips[*]}"
)
Get out of the habit of using ALLCAPS variable names, leave those as
reserved by the shell. One day you'll write PATH=something and then
wonder why
your script is broken.
I just noticed, if you just want a to join the IP addresses with commas, and you're using an array, you don't need a loop at all:
all_ips=$(
IFS=,
set -- "$server_ip" "${subnet_ip[#]}"
echo "$*"
)
You can use ${!varprefix#} or ${!varprefix*} to expand to all variables with that common prefix (the difference is the same as $# and $*):
SERVER_IP48_SUBNET=48sub
SERVER_IP49_SUBNET=49sub
SERVER_IP50_SUBNET=50sub
SERVER_IP=1.2.3.4
# set this as empty since !SERVER_IP# also matches SERVER_IP
ALLIPS=""
for var in "${!SERVER_IP#}"; do
ALLIPS=$ALLIPS,${!var}
done
This would probably be more practical if you could invert the names like this, since we can only match prefixes:
SERVER_IP_SUBNET_48=48sub
SERVER_IP_SUBNET_49=49sub
SERVER_IP_SUBNET_50=50sub
SERVER_IP=1.2.3.4
ALLIPS=$SERVER_IP
for var in "${!SERVER_IP_SUBNET_#}"; do
ALLIPS=$ALLIPS,${!var}
done
More info on this feature in the bash manual.
One idea:
SERVER_IP48_SUBNET=48sub
SERVER_IP49_SUBNET=49sub
SERVER_IP50_SUBNET=50sub
SERVER_IP=1.2.3.4
ALLIPs=${SERVER_IP}
for i in {48..50}
do
tmpvar="SERVER_IP${i}_SUBNET" # build the variable name
ALLIPs="${ALLIPs},${!tmpvar}" # indirect variable reference via tmpvar
done
echo "ALLIPs = $ALLIPs}"
This generates:
ALLIPs = 1.2.3.4,48sub,49sub,50sub
In my program I am trying to return a value from a function, the return value is string. Everything works fine(atleast some part), if I echo the value once it is returned, but it is not even calling the function, if I dont return.... Consider the code below....
#!/bin/bash
function get_last_name() {
echo "Get Last Name"
ipath=$1
IFS='/'
set $ipath
for item
do
last=$item
done
echo $last
}
main() {
path='/var/lib/iscsi/ifaces/iface0'
current=$(get_last_name "$path")
echo -n "Current="
echo $current
}
main
It gives me an output like this
OUTPUT
Current=Get Last Name iface0
If I comment the echo $current, then the I am not even seeing the "Get Last Name", which makes to come to conclusion, that it is not even calling the function. Please let me know what mistake I am making. But one thing I am sure, bash is the ugliest language I have ever seen.......
Functions do not have return values in bash. When you write
current=$(get_last_name "$path")
you are not assigning a return value to current. You are capturing the standard output of get_last_name (written using the echo command) and assigning it to current. That's why you don't see "Get last name"; that text does not make it to the terminal, but is stored in current.
Detailed explanation
Let's walk through get_last_name first (with some slight modifications to simplify the explanation):
function get_last_name () {
ipath=$1
local IFS='/'
set $ipath
for item
do
last=$item
done
echo "Get Last Name"
echo $last
}
I added the local command before IFS so that the change is confined to the body of get_last_name, and I moved the first echo to the end to emphasize the similarity between the two echo statements. When get_last_name is called, it processes its single argument (a string containing a file path), then echoes two strings: "Get Last Name" and the final component of the file path. If you were to run execute this function from the command line, it would appear something like this:
$ get_last_name /foo/bar/baz
Get Last Name
baz
The exit code of the function would be the exit code of the last command executed, in this case echo $last. This will be 0 as long as the write succeeds (which it almost certainly will).
Now, we look at the function main, which calls get_last_name:
main() {
path='/var/lib/iscsi/ifaces/iface0'
current=$(get_last_name "$path")
echo -n "Current="
echo $current
}
Just like with get_last_name, main will not have a return value; it will produce an exit code which is the exit code of echo $current. The function begins by calling get_last_name inside a command substitution ($(...)), which will capture all the standard output from get_last_name and treat it as a string.
DIGRESSION
Note the difference between the following:
current=$(get_last_name "$path")
sets the value of current to the accumulated standard output of get_last_name. (Among other things, newlines in the output are replaced with spaces, but the full explanation of how whitespace is handled is a topic for another day). This has nothing to do with return values; remember, the exit code (the closet thing bash has to "return values") is a single integer.
current=get_last_name "$path"
would not even call get_last_name. It would interpret "$path" as the name of a command and try to execute it. That command would have a variable current with the string value "get_last_name" in its environment.
The point being, get_last_name doesn't "return" anything that you can assign to a variable. It has an exit code, and it can write to standard output. The $(...) construct lets you capture that output as a string, which you can then (among other things) assign to a variable.
Back to main
Once the value of current is set to the output generated by get_last_name, we execute
two last echo statements to write to standard output again. The first writes "Current=" without a newline, so that the next echo statement produces text on the same line as the first. The second just echoes the value of current.
When you commented out the last echo of main, you didn't stop get_last_name from being executed (it had already been executed). Rather, you just didn't print the contents of the current variable, where the output of get_last_name was placed rather than on the terminal.
Hey so I have a bash command that echos a string based on reading some file. Say for simplicity it is like this
for line in `cat file`
do
if [ "$line" == "IwantThisLine" ]
then
echo "True"
fi
done
And I have it saved as its own individual script. Its called readRef.sh. So now I want to call it in matlab and store whatever it outputs in a variable! I am not entirely sure on how to do that, I seem to get an error when using evalc() on a system(). But it could be just me messing up quotations.
I tried something like
evalc(system(['./readRef.sh ' bamfile']))
The "bamfile" is a variable that is just a string to the path of a bamfile.
I get this error.
>> tes = evalc(system(['./readRef.sh ' smplBamFile]))
hg18
??? Undefined function or method 'evalc' for input arguments of type 'double'.
Coincidentally it does spit out "hg18" which is what I want to set the matlab variable to be.
Oh, I see. I don't think you need evalc at all. Reading the system docs you can just do:
[status, result] = system('echo True; echo "I got a loverly bunch of coconuts"')
And result will be
True
I got a loverly bunch of coconuts
So just do:
[status, result] = system(['./readRef.sh ' smplBamFile])
The reason evalc isn't working is that it requires its input to be a Matlab expression in a string, but you are passing it the result of system.
You could try:
evalc("system(['./readRef.sh ' smplBamFile])")
See how I'm passing in the system(...) as a string?
The reason you get this error is because system(...) returns the return-code of the command it ran, not its output. To capture its output, use
[~, output] = system(...)
tes = evalc(output);