Parameter Substitution replace with Alternation (OR) | - linux

I'm trying to replace a extension of a filename considering case but without success.
#!/bin/bash
pdf_file="/root/users/test.pdf"
jpg_file="${pdf_file/.pdf/.jpg}"
echo $jpg_file
I tried it, but it doesn't work:
jpg_file="${pdf_file/(.pdf|.PDF)/.jpg}"

You could use a glob pattern like this:
$ echo "${pdf_file/.[Pp][Dd][Ff]/.jpg}"
/root/users/test.jpg
If you use extended glob patterns (shopt -s extglob), you could use this instead:
$ echo "${pdf_file/.#(PDF|pdf)/.jpg}"
/root/users/test.jpg
Or you could use the shell option to ignore case when matching:
$ shopt -s nocasematch
$ pdf_file="/root/users/test.PDF"
$ echo "${pdf_file/.pdf/.jpg}"
/root/users/test.jpg
Remark valid for all three solutions: ${parameter/pattern/string} replaces the pattern wherever it occurs, but the extension is likely at the end – we can make sure we'll only replace it at the end:
echo "${pdf_file%.[Pp][Dd][Ff]}.jpg"
which works in any POSIX shell, or
shopt -s extglob
echo "${pdf_file%.#(PDF|pdf)}.jpg"
or
shopt -s nocasematch
pdf_file="/root/users/test.PDF"
echo "${pdf_file%.pdf}.jpg"

Related

I have pattern matching problem in case command in bash

I usually use pattern matching in [[ command,
but I wanted to use it in case command.
#!/bin/bash
bash -version|head -1
# Test 1:
[[ apple79 == apple#(14|38|79|11) ]] && echo ok 1
# Test2:
case apple79 in apple#(14|38|79|11)) echo ok 2;; *) ;; esac
When I run the above test.sh at terminal with . command everything is normal.
. test.sh
GNU bash, version 4.4.20(1)-release (x86_64-pc-linux-gnu)
ok 1
ok 2
But when I try to run it so:
./t.sh
GNU bash, version 4.4.20(1)-release (x86_64-pc-linux-gnu)
ok 1
./t.sh: line 6: syntax error near unexpected token `('
./t.sh: line 6: ` case apple79 in apple#(14|38|79|11)) echo ok 2;; *) ;; esac'
I have received an error messages for the case command.
I have seen some bash pattern matching doc eg.:
https://www.linuxjournal.com/content/pattern-matching-bash
But I have no any idea for this problem.
The extglob option is implicitly enabled inside [[ ... ]] for pattern matching, but you need to enable it explicitly to use it with the case statement.
#!/bin/bash
shopt -s extglob
[[ apple79 == apple#(14|38|79|11) ]] && echo ok 1
case apple79 in apple#(14|38|79|11)) echo ok 2;; *) ;; esac
The error is most likely because you have turned on extglob option in your current shell. Because sourcing the script takes the current shell's options and extended options, it works when sourcing the script.
But when doing the ./t.sh you are launching an explicit shell which does not have the option turned on by default. Since [[ operator with == turns on extglob by default, it works for the first test but fails for the case statement. To enable the option explicitly in scripts do shopt -s extglob at the top of your script.
As you can see below the pattern works with case only if the option is enabled. Try removing -O extglob from below command and you can see it doesn't work.
bash -O extglob -c 'case apple79 in apple#(14|38|79|11)) echo ok 2;; *) ;; esac'
As far why your attempt didn't work, try adding a line shopt extglob to your t.sh and repeat your tests. You'll notice that when the script is sourced you'll see extglob on and for the executed case get extglob off
The difference is between interactive and non-interactive shell options.
By default extglob are turned off for non-interactive shells, so you can do:
bash -i t.sh
or add
shopt -s extglob
at the beginning of your script

removing files except some in a bash script

I was trying to delete some files in a bash script and exclude some I still need afterwards. This is what I came up with:
if [ $var1 -eq 0 ]; then
echo "$var1 = 0"
shopt -s extglob
rm /home/someone/!(file1.txt|file2.png|file3.pdf|file4.csv)
else
echo "$var1 = 1"
shopt -s extglob
rm -rf /home/someone/!(file1.txt|file2.png|file3.pdf|file4.csv)
fi
It is working if I execute it manually on the shell, but in the script it does not even call the if-loop. As soon as I comment out the lines containing the "!" it works.
You cannot turn the extglob shell option from within a block or a function. E.g., this will fail:
shopt -u extglob
f() {
shopt -s extglob
echo *+(a)*
}
f
The reason is that the command shopt -s extglob is not executed when the block is parsed… and the parser will hence complain when it encounters the extended glob +(a). See the relevant section of the glob page on Greg's wiki:
extglob changes the way certain characters are parsed. It is necessary to have a newline (not just a semicolon) between shopt -s extglob and any subsequent commands to use it. You cannot enable extended globs inside a group command that uses them, because the entire block is parsed before the shopt is evaluated. Note that the typical function body is a group command. An unpleasant workaround could be to use a subshell command list as the function body.
If you really want this behavior, though, you can use eval, but that's really not recommended at all! Instead, move the shopt -s extglob out of the block. In fact, it is customary to put the shell options at the beginning of the script and use them throughout the script (when applicable). I don't think you'll run into any problems if you use extglob throughout the script.
As #choroba is hinting towards, you need to run your script either as bash my_script.sh or ./my_script.sh to run it with Bash. If you run sh my_script.sh the shell may not support extglob.
A useful way to tell what's actually happening is to add set -o xtrace to the top of the script (after the shebang line). That should tell you what actually gets executed. I'd also add set -o errexit to make sure the script stops at the first failing command, and quote the reference to $var1.

How do I echo "-e"?

I want to echo a string that might contain the same parameters as echo. How can I do it without modifying the string?
For instance:
$ var="-e something"
$ echo $var
something
... didn't print -e
A surprisingly deep question. Since you tagged bash, I'll assume you mean bash's internal echo command, though the GNU coreutils' standalone echo command probably works similarly enough.
The gist of it is: if you really need to use echo (which would be surprising, but that's the way the question is written by now), it all depends on what exactly your string can contain.
The easy case: -e plus non-empty string
In that case, all you need to do is quote the variable before passing it to echo.
$ var="-e something"
$ echo "$var"
-e something
If the string isn't eaxctly an echo option or combination, which includes any non-option suffix, it won't be recognized as such by echo and will be printed out.
Harder: string can be -e only
If your case can reduce to just "-e", it gets trickier. One way to do it would be:
$ echo -e '\055e'
-e
(escaping the dash so it doesn't get interpreted as an option but as on octal sequence)
That's rewriting the string. It can be done automatically and non-destructively, so it feels acceptable:
$ var="-e something"
$ echo -e ${var/#-/\\055}
-e something
You noticed I'm actually using the -e option to interpret an octal sequence, so it won't work if you intended to echo -E. It will work for other options, though.
The right way
Seriously, you're not restricted to echo, are you?
printf '%s\n' "$var"
The proper bash way is to use printf:
printf "%s\n" "$var"
By the way, your echo didn't work because when you run:
var="-e something"
echo $var
(without quoting $var), echo will see two arguments: -e and something. Because when echo meets -e as its first argument, it considers it's an option (this is also true for -n and -E), and so processes it as such. If you had quoted var, as shown in other answers, it would have worked.
Quote it:
$ var="-e something"
$ echo "$var"
-e something
If what you want is to get echo -e's behaviour (enable interpretation of backslash escapes), then you have to leave the $var reference without quotes:
$ var="hi\nho"
$ echo $var
hi
ho
Or use eval:
$ var="hi\nho"
$ eval echo \${var}
hi\nho
$ var="-e hi\nho"
$ eval echo \${var}
hi
ho
Since we're using bash, another alternative to echo is to simply cat a "here string":
$ var="-e something"
$ cat <<< "$var"
-e something
$ var="-e"
$ cat <<< "$var"
-e
$
printf-based solutions will almost certainly be more portable though.
Try the following:
$ env POSIXLY_CORRECT=1 echo -e
-e
Due to shell aliases and built-in echo command, using an unadorned
echo interactively or in a script may get you different functionality
than that described here. Invoke it via env (i.e., env echo ...)
to avoid interference from the shell.
The environment variable POSIXLY_CORRECT was introduced to allow the user to force the standards-compliant behaviour. See: POSIX at Wikipedia.
Or use printf:
$ printf '%s\n' "$var"
Source: Why is bash swallowing -e in the front of an array at stackoverflow SE
Use printf instead:
var="-e bla"
printf "%s\n" "$var"
Using just echo "$var" will still fail if var contains just a -e or similar. If you need to be able to print that as well, use printf.

don't execute for file in ls do

I have a script that has to process some files (name beginning with AB) in a directory.
The code is :
for file in AB*
do
cp ...
...
done
When there are no *.txt files in the folder the code executes anyway 1 time.
But then there are errors because I try to copy a file that doesn't exist.
How can I make that the do-command doesn't execute when the result of the ls-command is empty?
I already tried using ls, quotes an combinations > nothing gives the result I want.
Maybe you can add a condition before:
if [ $(ls AB* 2>/dev/null) ]; then
for ...
fi
with 2>/dev/null you catch the errors not to be printed.
The other answers are just plain wrong in Bash. Do not use them! Please, always observe this rule:
Each time you use globs in Bash, use them with either shopt -s nullglob or shopt -s failglob.
If you observe this rule, you'll always be safe. In fact, each time you don't observe this rule, God kills a kitten.
shopt -s nullglob: in this case, a non-matching glob expands to nothing. Look:
$ mkdir Test; cd Test
$ shopt -u nullglob # I'm explicitly unsetting nullglob
$ echo *
*
$ for i in *; do echo "$i"; done
*
$ # Dear, God has killed a kitten :(
$ # but it was only for demonstration purposes, I swear!
$ shopt -s nullglob # Now we're going to save lots of kittens
$ echo *
$ for i in *; do echo "$i"; done
$ # Wow! :)
shopt -s failglob: in this case, Bash will raise an explicit error when a glob has no expansions. Look:
$ mkdir Test; cd Test
$ shopt -u nullglob # Unsetting nullglob
$ shopt -s failglob # Setting failglob for the love of kittens
$ echo *
bash: no match: *
$ # cool :) what's the return code of this?
$ echo $?
1
$ # who cares, anyway? and a for loop?
$ for i in *; do echo "$i"; done
bash: no match: *
$ # cool :)
Using either nullglob or failglob, you're sure to not launch random commands with uncontrolled arguments!
Cheers!
You probably need the bash test builtin, often abbreviated as [ , sothing like
if [ -f output.txt ] ; then
beware: spaces are important above.

Validate script's argument by file extension?

I am writing a script which you can pass a file name into as an argument and it'll only run if it's a certain file extension.
flac2mp3 "01 Song.flac"
or
flac2mp3 "01 Song.FLAC"
I know there a lot of scripts out there showing you how to convert flac to mp3, but this is my script and I want to learn how to write the script using this method.
It's so I can learn arguments and for when I feel like converting only 1 individual file. (for multiple files I just wrote a for loop with *.flac inside the script)
I just want to learn how to check if the $1 argument contains *.[Ff][Ll][Aa][Cc]
Here's what I cobbled up together from the internet so far (which I know is embarrassingly wrong but I wanted to show what I was going for) :
#!/bin/bash
#flac2mp3
if [ -z $1 ] && [[$1 !=~ *.[Ff][Ll][Aa][Cc]]];then echo "Give FLAC File Name"; exit 0;fi
OUTF=${1%.flac}.mp3
ARTIST=$(metaflac "$1" --show-tag=ARTIST | sed s/.*=//g)
TITLE=$(metaflac "$1" --show-tag=TITLE | sed s/.*=//g)
ALBUM=$(metaflac "$1" --show-tag=ALBUM | sed s/.*=//g)
GENRE=$(metaflac "$1" --show-tag=GENRE | sed s/.*=//g)
TRACKNUMBER=$(metaflac "$1" --show-tag=TRACKNUMBER | sed s/.*=//g)
DATE=$(metaflac "$1" --show-tag=DATE | sed s/.*=//g)
flac -c -d "$1" | lame -m j -q 0 --vbr-new -V 0 -s 44.1 - "$OUTF"
id3 -t "$TITLE" -T "${TRACKNUMBER:-0}" -a "$ARTIST" -A "$ALBUM" -y "$DATE" -g "${GENRE:-12}" "$OUTF"
done
Please and Thank Your for the help.
Try the following code:
shopt -s nocasematch
if [[ $1 == *flac ]]; then
echo "ok"
fi
This is case insensitive.
EDIT
$ LANG=C help shopt
shopt: shopt [-pqsu] [-o] [optname ...]
Set and unset shell options.
Change the setting of each shell option OPTNAME. Without any option
arguments, list all shell options with an indication of whether or not each
is set.
Options:
-o restrict OPTNAMEs to those defined for use with `set -o'
-p print each shell option with an indication of its status
-q suppress output
-s enable (set) each OPTNAME
-u disable (unset) each OPTNAME
Exit Status:
Returns success if OPTNAME is enabled; fails if an invalid option is
given or OPTNAME is disabled.
If you run shopt alone in a shell, you will see al options available :
$ shopt
autocd on
cdable_vars on
cdspell off
checkhash off
checkjobs off
checkwinsize off
cmdhist on
compat31 off
compat32 off
compat40 off
compat41 off
direxpand off
dirspell off
dotglob on
execfail off
expand_aliases on
extdebug off
extglob on
extquote on
failglob off
force_fignore on
globstar on
gnu_errfmt off
histappend on
histreedit off
histverify off
hostcomplete off
huponexit off
interactive_comments on
lastpipe off
lithist off
login_shell off
mailwarn off
no_empty_cmd_completion off
nocaseglob off
nocasematch off
nullglob off
progcomp on
promptvars on
restricted_shell off
shift_verbose off
sourcepath on
xpg_echo off
To know what does all these options :
man bash | less +/'^SHELL BUILTIN COMMANDS'
then search `shopt from within this section.

Resources