Joining files with cat - bash

i'm doing a script to download one file in multiple parts, i'm in the part of joining that files. I can join the parts outside of the script with
cat name.part{0..4} > name.ext
But, if i use this in the script
cat $filename.part{0..$N} > $filename
i get:
cat: Bitcoin-960x623.jpg.part{0..5}: No such file or

{0..4} is a brace expansion; $N is a variable expansion. Your expression doesn't work as you expect because brace expansion happens before variable expansion:
The order of expansions is: brace expansion, tilde expansion, parameter, variable, and arithmetic expansion and command substitution (done in a left-to-right fashion), word splitting, and filename expansion.
http://www.gnu.org/software/bash/manual/bashref.html#Shell-Expansions
You can use the seq command instead of a brace expansion to generate the required filenames up to arbitrary N:
$ filename=myfile
$ N=4
$ cat $(seq -f "$filename.part%g" 0 $N) > name.ext
$
In the unlikely event seq -f is unavailable, you can use a for loop one-liner instead:
$ cat $(for ((i=0; i<N; i++)); do echo "$filename.part$i"; done) > name.ext
$

I solved with this.
for (( k=0 ; k<=$N ; k++ ))
do
cat $filename.part$k >> $filename
done

You cannot use variables inside curly braces in shell. eval can be used here:
eval "$filename.part{0..$N} > $filename"

Assertion: I'm going to argue that this answer is better. (Something I don't do often or lightly.) My rationale is that all of the other answers here at the time of this edit just expand to a range of integers, whether the files matching those integers exist or not. By using glob expansions you guarantee you will not encounter No such file or directory from cat. In all other cases with brace expansion, eval, seq and a for loop you'd have to explicitly check.
If there are fewer than 10 total filenames:
cat "$filename".part[0-9]
will expand as a glob to however many names there are. This is a fast and portable solution, but obviously limited. If there are more than 10 then that pattern will only match the ones with one digit, so you have to get a bit wilier and use extglob:
shopt -s extglob
cat "$filename".part+([0-9])
should do it, but this will only work for the Bourne-Again SHell. (That said, several other modern shells have expansion capabilities similar to extglob.)

Related

How can I concatenate a var with curly brackets in bash [duplicate]

This question already has answers here:
Brace expansion with variable? [duplicate]
(6 answers)
Closed 4 years ago.
Consider the following script:
#! /bin/bash -e
echo {foo,bar}
EX={foo,bar}
echo ${EX}
The output of this script is:
foo bar
{foo,bar}
I would like the the echo command to perform brace expansion on ${EX}. Thus, I would like to see an output of
foo bar
foo bar
I want to create a script where the user can supply a path with curly brackets where every expanded version of it is copied.
Something like this:
#! /bin/bash -e
$SOURCES=$1
$TARGET=$2
cp -r ${SOURCES} ${TARGET}
How can I achieve this?
This is a way:
ex=({foo,bar,baz})
echo ${ex[#]}
foo bar baz
See man bash:
The order of expansions is: brace expansion, tilde expansion, parameter, variable and arithmetic expansion and command substitution (done in a left-to-right fashion), word splitting, and pathname expansion.
As you see, variable expansion happens later than brace expansion.
Fortunately, you don't need it at all: let the user specify the braced paths, let the shell expand them. You can then just
mv "$#"
If you need to separate the arguments, use an array and parameter expansion:
sources=("${#:1:$#-1}")
target=${#: -1}
mv "${sources[#]}" "$target"
Brace expansion does not work the way you are attempting to use it. Brace expansion is basically used to generate lists to be applied within the context of the present command. You have two primary modes where brace expansion is used directly (and many more where brace expansion is used a part of another operator.) The two direct uses are to expand a list of items within a comma-separated pair of braces. e.g.
$ touch file_{a,b,c,d}.txt
After executing the command, brace expansion creates all four files with properly formatted file names in the present directory:
$ ls -1 file*.txt
file_a.txt
file_b.txt
file_c.txt
file_d.txt
You may also use brace-expansion in a similar manner to generate lists for loop iteration (or wherever a generated system/range of numbers in needed). The syntax for using brace expansion here is similar, but with .. delimiters within the braces (instead of ',' separation). The syntax is {begin..end..increment} (whereincrement can be both positive and negative) e.g.
$ for i in {20..-20..-4}; do echo $i; done)
20
16
12
8
4
0
-4
-8
-12
-16
-20
(note: using variables for begin, end or increment is not allowed without some horrible eval trickery -- avoid it.).

How to store curly brackets in a Bash variable

I am trying to write a bash script. I am not sure why in my script:
ls {*.xml,*.txt}
works okay, but
name="{*.xml,*.txt}"
ls $name
doesn't work. I get
ls: cannot access {*.xml,*.txt}: No such file or directory
The expression
ls {*.xml,*.txt}
results in Brace expansion and shell passes the expansion (if any) to ls as arguments. Setting shopt -s nullglob makes this expression evaluate to nothing when there are no matching files.
Double quoting the string suppresses the expansion and shell stores the literal contents in your variable name (not sure if that is what you wanted). When you invoke ls with $name as the argument, shell does the variable expansion but no brace expansion is done.
As #Cyrus has mentioned, eval ls $name will force brace expansion and you get the same result as that of ls {\*.xml,\*.txt}.
The reason your expansion doesn't work is that brace expansion is performed before variable expansion, see Shell expansions in the manual.
I'm not sure what it is you're trying to do, but if you want to store a list of file names, use an array:
files=( {*.txt,*.xml} ) # these two are the same
files=(*.txt *.xml)
ls -l "${files[#]}" # give them to a command
for file in "${files[#]}" ; do # or loop over them
dosomething "$file"
done
"${array[#]}" expands to all elements of the array, as separate words. (remember the quotes!)

How can I do brace expansion on variables? [duplicate]

This question already has answers here:
Brace expansion with variable? [duplicate]
(6 answers)
Closed 4 years ago.
Consider the following script:
#! /bin/bash -e
echo {foo,bar}
EX={foo,bar}
echo ${EX}
The output of this script is:
foo bar
{foo,bar}
I would like the the echo command to perform brace expansion on ${EX}. Thus, I would like to see an output of
foo bar
foo bar
I want to create a script where the user can supply a path with curly brackets where every expanded version of it is copied.
Something like this:
#! /bin/bash -e
$SOURCES=$1
$TARGET=$2
cp -r ${SOURCES} ${TARGET}
How can I achieve this?
This is a way:
ex=({foo,bar,baz})
echo ${ex[#]}
foo bar baz
See man bash:
The order of expansions is: brace expansion, tilde expansion, parameter, variable and arithmetic expansion and command substitution (done in a left-to-right fashion), word splitting, and pathname expansion.
As you see, variable expansion happens later than brace expansion.
Fortunately, you don't need it at all: let the user specify the braced paths, let the shell expand them. You can then just
mv "$#"
If you need to separate the arguments, use an array and parameter expansion:
sources=("${#:1:$#-1}")
target=${#: -1}
mv "${sources[#]}" "$target"
Brace expansion does not work the way you are attempting to use it. Brace expansion is basically used to generate lists to be applied within the context of the present command. You have two primary modes where brace expansion is used directly (and many more where brace expansion is used a part of another operator.) The two direct uses are to expand a list of items within a comma-separated pair of braces. e.g.
$ touch file_{a,b,c,d}.txt
After executing the command, brace expansion creates all four files with properly formatted file names in the present directory:
$ ls -1 file*.txt
file_a.txt
file_b.txt
file_c.txt
file_d.txt
You may also use brace-expansion in a similar manner to generate lists for loop iteration (or wherever a generated system/range of numbers in needed). The syntax for using brace expansion here is similar, but with .. delimiters within the braces (instead of ',' separation). The syntax is {begin..end..increment} (whereincrement can be both positive and negative) e.g.
$ for i in {20..-20..-4}; do echo $i; done)
20
16
12
8
4
0
-4
-8
-12
-16
-20
(note: using variables for begin, end or increment is not allowed without some horrible eval trickery -- avoid it.).

Assigning variables in for loop in bash

The following bash script works fine to print numbers from 1 to 10:
for i in {1..10}
do
echo "$i"
done
But if I want to make the upper limit a variable, then this script does not works.
i=10
for j in {1..$i}
do
echo "$j"
done
Can anyone suggest please how to make the second script work?
Brace expansion happens before any other expansion.
You can say:
for j in $(seq 1 $i); do echo "$j"; done
Quoting from the link above:
Brace expansion is performed before any other expansions, and any
characters special to other expansions are preserved in the result. It
is strictly textual. Bash does not apply any syntactic interpretation
to the context of the expansion or the text between the braces. To
avoid conflicts with parameter expansion, the string ‘${’ is not
considered eligible for brace expansion.
You cannot use variables in {..} directive of Bash. Use BASH arithmetic operator ((...)) like this:
i=10
for ((j=1; j<=i; j++)); do
echo "$j"
done

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