BASH: Path difference between two paths? - bash

Say I have the paths
a/b/c/d/e/f
a/b/c/d
How do I get the below?
e/f

You can strip one string from the other with:
echo "${string1#"$string2"}"
See:
$ string1="a/b/c/d/e/f"
$ string2="a/b/c/d"
$ echo "${string1#"$string2"}"
/e/f
From man bash -> Shell parameter expansion:
${parameter#word}
${parameter##word}
The word is expanded to produce a pattern just as in filename
expansion. If the pattern matches the beginning of the expanded value
of parameter, then the result of the expansion is the expanded value
of parameter with the shortest matching pattern (the ‘#’ case) or the
longest matching pattern (the ‘##’ case) deleted.
With spaces:
$ string1="hello/i am here/foo/bar"
$ string2="hello/i am here/foo"
$ echo "${string1#"$string2"}"
/bar
To "clean" multiple slashes, you can follow Roberto Reale's suggestion and canonicalize the paths with readlink -m to allow comparison with strings with the same real path up:
$ string1="/a///b/c//d/e/f/"
$ readlink -m $string1
/a/b/c/d/e/f

Related

what's the meaning of "${0##*[\\/]}" in shell?

I got the question when i looking other's shell script.
I saw that declared
APP_NAME="${0##*[\\/]}"
Since I can't find any answer, what's the meaning of this code?
It's Shell Parameter Expansion to get script name itself, without path
See bash manual:
${parameter##word}
The word is expanded to produce a pattern and matched according to the rules described below (see Pattern Matching).
If the pattern matches the beginning of the expanded value of parameter, then the result of the expansion is the expanded value of parameter with the shortest matching pattern (the ‘#’ case) or the longest matching pattern (the ‘##’ case) deleted.
Pattern Matching:
*
Matches any string, including the null string. When the globstar shell option is enabled, and ‘*’ is used in a filename expansion context
[…]
Matches any one of the enclosed characters.
Explanation
${parameter<...>} expression means that you can expand shell parameters.
I.e. ${1:-"default_arg_value"} will be expanded to "default_arg_value" if script running without arguments.
0 - is a 0th argument, i.e. script name itself
${0##<pattern>} will delete longest matching to <pattern> part of $0
*[\\/] means any string that ends with \ or / symbol.
So, APP_NAME="${0##*[\\/]}" means that $APP_NAME will be initialized by script name itself, without path.
Sample
Let's suppose you have script a/b/c/d/test.sh:
#!/bin/bash
echo "${0##*[\/]}"
echo "${1#*[\/]}"
echo "${2##[\/]}"
$ bash a/b/c/d/test.sh /tmp/e /tmp/ff
> test.sh
> tmp/e
> tmp/ff

What's the meaning of this bash parameter expansion syntax?

In the golang GOPATH documentation it instructs you to add the following to your bash PATH:
${GOPATH//://bin:}/bin
What does the //://bin: mean here?
$GOPATH appears to evaluate to the same value as ${GOPATH//://bin:}
My first guess was that this somehow expands into all subdirectories of $GOPATH that have a /bin subdirectory, but this does not appear to be the case. I added some subdirectories to my $GOPATH with a /bin dir, and the above expression does not include them.
I dug into my manpage for bash and didn't see any hints.
man bash says under PARAMETER EXPANSION:
${parameter/pattern/string}
Pattern substitution. The pattern is expanded to produce a pattern just as in
pathname expansion. Parameter is expanded and the longest match of pattern against
its value is replaced with string. If pattern begins with /, all matches of pattern are replaced with string. Normally only the first match is replaced.
e.g.
$ GOPATH=a:b:c
$ echo ${GOPATH//://bin:}
a/bin:b/bin:c

Confusion about bash parameter substitution

I have a script to rename a file or a series of files that contain a specific string
for i in "${#:3}"; do
mv -- "$i" "${i/$1/$2}"
done
so when i do
bash script_name patter_1 pattern_2 string*
it does work but when i try:
mv -- "$i" "${i//$1/$2}"
it still works
${i/$1/$2}
and
${i//$1/$2}
so why is that happening, i search bash guide for beginner but still have no clue. Thank you
From the bash manual:
${parameter/pattern/string}
The pattern is expanded to produce a pattern just as in filename
expansion. Parameter is expanded and the longest match of pattern
against its value is replaced with string. If pattern begins with
‘/’, all matches of pattern are replaced with string
So if the replacement can be done just once, these are equivalent:
${parameter/pattern/string}
^
${parameter//pattern/string}
^^
See an example:
$ i="hello"
$ echo ${i/e/XX} #just one replacement
hXXllo # <----------------------
$ echo ${i//e/XX} # multiple replacements | they are the same
hXXllo # <----------------------
$ echo ${i/l/XX} #just one replacement
heXXlo # it just happens once!
$ echo ${i//l/XX} #multiple replacements
heXXXXo # it happens many times!

Bash script to convert music from one directory into another

I've modified this script from the arch forums: https://wiki.archlinux.org/index.php/Convert_Flac_to_Mp3#With_FFmpeg
I'm trying to find specific file types in a directory structure, convert them to another music file type, and place them in a "converted" directory that maintains the same directory structure.
I'm stuck at stripping the string $b of its file name.
$b holds the string ./converted/alt-j/2012\ an\ awesome\ wave/01\ Intro.flac
Is there a way I can remove the file name from the string? I don't think ffmpeg can create/force parent directories of output files.
#!/bin/bash
# file convert script
find -type f -name "*.flac" -print0 | while read -d $'\0' a; do
b=${a/.\//.\/converted/}
< /dev/null ffmpeg -i "$a" "${b[#]/%flac/ogg}"
#echo "${b[#]/%flac/ogg}"
I'm stuck at stripping the string $b of its file name.
Let us start with b:
$ b=./converted/alt-j/2012\ an\ awesome\ wave/01\ Intro.flac
To remove the file name, leaving the path:
$ c=${b%/*}
To verify the result:
$ echo "$c"
./converted/alt-j/2012 an awesome wave
To make sure that directory c exists, do:
$ mkdir -p "$c"
Or, all in one step:
$ mkdir -p "${b%/*}"
How it works
We are using the shell's suffix removal feature. In the form ${parameter%word}, the shell finds the shortest match of word against the end of parameter and removes it. (Note that word is a shell glob, not a regex.) In out case, word is /* which matches a slash followed by any characters. Because this removes the shortest such match, this removes only the filename part from the parameter.
Suffix Removal Detailed Documentation
From man bash:
${parameter%word} ${parameter%%word}
Remove matching suffix pattern. The word is expanded to produce a pattern just as in pathname expansion. If the pattern
matches a trailing portion of the expanded value of parameter, then the result of the expansion is the expanded value of
parameter with the shortest matching pattern (the %'' case) or the longest matching pattern (the%%'' case) deleted.
If parameter is # or *, the pattern removal operation is applied to each positional parameter in turn, and the expansion
is the resultant list. If parameter is an array variable subscripted with # or *, the pattern removal operation is
applied to each member of the array in turn, and the expansion is the resultant list.

how to match more than one word in bash

I'd like list files with the name pattern like [max|min].txt, so execute
ls [max|min].txt in bash shell, but it doesn't work, and the error message I got is:
ls: cannot access [max: No such file or directory
so what's the right way to do this job?
Square brackets are for character matching, and vertical bars are for pipes. You're looking for brace expansion.
ls {max,min}.txt
Bash has a shell option called extglob that you can enable with the command shopt -s extglob. This will allow you to use the pattern format #(pattern-list) where pattern-list is a pipe separated list of patterns. It will match against filenames and will exclude any pattern that does not match a filename, just like the [abc] range expression. Bash also has brace expansion, but this does not appear to be what you are asking for, as brace expansion does not match against filenames or expand like wildcards or range expressions do.
$ shopt -s extglob
$ touch max.txt min.txt
$ echo #(max|min).txt
max.txt min.txt
$ echo #(min|mid|max).txt
max.txt min.txt
$ echo {min,mid,max}.txt
min.txt mid.txt max.txt
A couple of things to note about the sequence of commands above:
echo #(mid|min|max).txt does not output mid.txt because there is no file that matches.
echo #(min|mid|max).txt re-orders the output to be sorted, in the same manner as a wildcard expansion.
echo {min,mid,max}.txt is brace expansion and outputs all elements in the order given.

Resources