using glob to substitute a variable - bash

After reading some docs from globs in bash
https://mywiki.wooledge.org/glob#extglob
I´m struggling to replace a part of a variable:
Just an example that i am using to learn a bit more
myvar="value1=aaa value2=bbb value3=ccc value4=ddd"
value2="zzz"
myvar="${myvar//value2=*([a-z0-9-])/value2=${value2}}"
result:
echo $myvar
value1=aaa value2=zzz
expected result:
echo $myvar
value1=aaa value2=zzz value3=ccc value4=ddd
The result of the code above is not the expected one as it´s deleting everything behind value2=bbb in myvar.
If I use:
shopt -s extglob
it is working from a virtual machine with linux but from my current machine is not even if the begining of the script starts with #!/bin/bash. Most probably because i´m not using bash in my terminal.
Any other way to achieve this? (with sed for example without enabling extglob)
thx

If you really can't use Bash, then you can maybe do what you want with lowest common denominator shell features like this:
pre=${myvar%%value2=*}
post=${myvar#"$pre"value2=}
v2=${post%%[!a-z0-9-]*}
post=${post#"$v2"}
myvar="${pre}value2=${value2}${post}"

Related

How to continue with nested for loops with a "zsh: no matches found" error? [duplicate]

I'm currently working on a script that deletes all the PNG files from my Desktop. I want to create an array of file paths then use the rm command on each one.
This is the relevant bit of code:
#!/usr/bin/env bash
shopt -s nullglob
files=("$HOME"/Desktop/*.png)
files_found="${#files[#]}"
shopt -u nullglob
It has been recommend that I use shopt in case of no matching files.
However I'm on MacOS and just discovered that shopt is not available for ZSH. When I run the script I get command not found: shopt.
I've found the ZSH has an equivalent called setopt however after reading through the documentation I can't quite figure out which option is the correct one to use in the case. I can't seem to find any examples either.
Can anyone point me in the right direction?
The corresponding option in zsh is CSH_NULL_GLOB (documented in man zshoptions).b
setopt CSH_NULL_GLOB
(As far as I can tell, the idea of a pattern disappearing rather than being treated literally comes from csh.)
The more zsh-like approach is not to set this as a general option (as suggested in the answer given by chepner), but to decide on each pattern, whether or you want to have the nullglob effect. For example,
for f in x*y*(N)
do
echo $f
done
simply skips the loop if there are no files matching the pattern.
Just come to the realisation that the issue of shopt not being found was due to me auto-loading the file as a ZSH function.
The script worked perfectly when I ran it like so:
bash ./tidy-desktop
Previously I had been running it just with the command tidy-desktop
Instead I now have this in my zsh_aliases:
tidy-desktop="~/.zshfn/tidy-desktop"
Thanks to #Charles Duffy for helping me figure out what was going on there!

Is there a ZSH equivalent to "shopt -s nullglob"?

I'm currently working on a script that deletes all the PNG files from my Desktop. I want to create an array of file paths then use the rm command on each one.
This is the relevant bit of code:
#!/usr/bin/env bash
shopt -s nullglob
files=("$HOME"/Desktop/*.png)
files_found="${#files[#]}"
shopt -u nullglob
It has been recommend that I use shopt in case of no matching files.
However I'm on MacOS and just discovered that shopt is not available for ZSH. When I run the script I get command not found: shopt.
I've found the ZSH has an equivalent called setopt however after reading through the documentation I can't quite figure out which option is the correct one to use in the case. I can't seem to find any examples either.
Can anyone point me in the right direction?
The corresponding option in zsh is CSH_NULL_GLOB (documented in man zshoptions).b
setopt CSH_NULL_GLOB
(As far as I can tell, the idea of a pattern disappearing rather than being treated literally comes from csh.)
The more zsh-like approach is not to set this as a general option (as suggested in the answer given by chepner), but to decide on each pattern, whether or you want to have the nullglob effect. For example,
for f in x*y*(N)
do
echo $f
done
simply skips the loop if there are no files matching the pattern.
Just come to the realisation that the issue of shopt not being found was due to me auto-loading the file as a ZSH function.
The script worked perfectly when I ran it like so:
bash ./tidy-desktop
Previously I had been running it just with the command tidy-desktop
Instead I now have this in my zsh_aliases:
tidy-desktop="~/.zshfn/tidy-desktop"
Thanks to #Charles Duffy for helping me figure out what was going on there!

Why bash script does not run well?

I write a code in Bash script and the Linux does not run it correctly and printed character instead of character’s value.
Can anyone help me?
Aside from the confusion between backtick and ', you are also over-using the sub-shell syntax. You do not need to use echo $(cat $LOOP). You can just run cat $LOOP directly.
#!/bin/bash
for FILE in $(ls); do
echo "Here is ${file}:"
cat ${FILE}
echo ""
done
A couple of points of style as well:
Name your variables after the thing they represent. The loop is iterating over files in the current directory, so the name FILE is more descriptive than LOOP.
It is a good idea to get in the habit of enclosing your variable references in ${ ... } instead of just prefixing them with $. In simple scripts like this, it does not make a difference, but when your scripts get more complicated, you can get into trouble when you do not clearly delineate variable names.

Choose placeholders for substitution inside a bash script

NOTE: The goal of this question is to find a suitable character sequence for an effective placeholder substitution in a bash script, not finding if a command is evaluated or not.
I have a skeleton named my_script.skelof a bash script in which I have to put a placeholder. I want to find a placeholder sequence that can be safely substituted (I mean that there aren't any clashes with other bash commands or other pathological substitutions, see A non-safe example for an example).
I've figured out on my own that enveloping placeholder_name within #~ and ~# seems a good solution, but I'm not sure that this solution is safe in every possible case.
A non-safe example
One can decide to use /placeholder_name/. So my_dumb_script.skel is:
#!/bin/bash
AN_INNOCENT_PATH="/a/simple/placeholder_name/path"
echo /placeholder_name/
The goal is to replace only /placeholder_name/ in the echo command. If now I use sed on the placeholder:
sed 's%/placeholder_name/%foobar%g' my_dumb_script.skel > output.bash
The output could be unexpected:
#!/bin/bash
AN_INNOCENT_PATH="/a/simplefoobarpath"
echo foobar
In this case we've obtained an unwanted substitution inside AN_INNOCENT_PATH, since it's easy to pattern-match on placeholders that contains the / character. I know that it seems very dumb, but you cannot know how people will use your code in the future (and someone could create a folder named placeholder_name).
A safe example that uses #~
In this case my_better_script.skel is the following one:
#!/bin/bash
AN_INNOCENT_PATH="/a/simple/placeholder_name/path"
echo #~placeholder_name~#
And now we can use sed:
sed 's%#~placeholder_name~#%foobar%g' my_better_script.skel > output.bash
The output now is better:
#!/bin/bash
AN_INNOCENT_PATH="/a/simple/placeholder_name/path"
echo foobar
Now everything works as intended.
You can use type to check if a command will evaluate.
$ placeholder1="testing"
$ placeholder2="test"
$ type $placeholder1
-bash: type: testing: not found
$ type $placeholder2
test is a shell builtin

Iteration in bash is not working

I am trying to run the next code on bash. It is suppose to work but it does not.
Can you help me to fix it? I am starting with programming.
The code is this:
for i in {1:5}
do
cd path-to-folder-number/"$i"0/
echo path-to-folder-number/"$i"0/
done
EXAMPLE
I want to iterate over folders which have numbers (10,20..50), and so it changes directory from "path-to-folder-number/10/" to "path-to-folder-number/20/" ..etc
I replace : with .. but it is not working yet. When the script is applied i get:
can't cd to path-to-folder-number/{1..5}0/
I think there are three problems here: you're using the wrong shell, the wrong syntax for a range, and if you solved those problems you may also have trouble with successive cds not doing what you want.
The shell problem is that you're running the script with sh instead of bash. On some systems sh is actually bash (but running in POSIX compatibility mode, with some advanced features turned off), but I think on your system it's a more basic shell that doesn't have any of the bash extensions.
The best way to control which shell a script runs with is to add a "shebang" line at the beginning that says what interpreter to run it with. For bash, that'd be either #!/bin/bash or #!/usr/bin/env bash. Then run the script by either placing it in a directory that's in your $PATH, or explicitly giving the path to the script (e.g. with ./scriptname if you're in the same directory it's in). Do not run it with sh scriptname, because that'll override the shebang and use the basic shell, and it won't work.
(BTW, the name "shebang" comes from the "#!" characters the line starts with -- the "#" character is sometimes called "sharp", and "!" is sometimes called "bang", so it's "sharp-bang", which gets abbreviated to "shebang".)
In bash, the correct syntax for a range in a brace expansion is {1..5}, not {1:5}. Note that brace expansions are a bash extension, which is why getting the right shell matters.
Finally, a problem you haven't actually run into yet, but may when you get the first two problems fixed: you cd to path-to-folder-number/10/, and then path-to-folder-number/20/, etc. You are not cding back to the original directory in between, so if the path-to-folder-number is relative (i.e. doesn't start with "/"), it's going to try to cd to path-to-folder-number/10/path-to-folder-number/20/path-to-folder-number/30/path-to-folder-number/40/path-to-folder-number/50/.
IMO using cd in scripts is generally a bad idea, because there are a number of things that can go wrong. It's easy to lose track of where the script is going to be at which point. If any cd fails for any reason, then the rest of the script will be running in the wrong place. And if you have any files specified by relative paths, those paths become invalid as soon as you cd someplace other than the original directory.
It's much less fragile to just use explicit paths to refer to file locations within the script. So, for example, instead of cd "path-to-folder-number/${i}0/"; ls, use ls "path-to-folder-number/${i}0/".
For up ranges the syntax is:
for i in {1..5}
do
cd path-to-folder-number/"$i"0/
echo $i
done
So replace the : with ..
To get exactly what you want you can use this:
for i in 10 {20..50}
do
echo $i
done
You can also use seq :
for i in $(seq 10 10 50); do
cd path-to-folder-number/$i/
echo path-to-folder-number/$i/
done

Resources