logical or | Unix - bash

I'm trying to realize a simple shell program that display french phone numbers contained in a file.
Here is my basic shell
#!/bin/bash
#search of phone numbers
t=( \+ | 00 )33[1-9][0-9]{8}
t2=( \+ | 00 )33[1-9][0-9]-[0-9]{2}-[0-9]{2}-[0-9]{2}-[0-9]{2}
t3=( \+ | 00 )33[1-9][0-9].[0-9]{2}.[0-9]{2}.[0-9]{2}.[0-9]{2}
grep -e $1 ( $t | $t2 | $t3 )
Here is my input file :
phone_number.txt
+33143730862
00335.45.45.45.45
+332-45-45-45-45
+334545454554454545
I keep getting this errors :
./script_exo2.sh: line 5: syntax error near unexpected token `|'
./script_exo2.sh: line 5: `t=( \+ | 00 )33[1-9][0-9]{8}'
./script_exo2.sh: line 6: syntax error near unexpected token `|'
./script_exo2.sh: line 6: `t2=( \+ | 00 )33[1-9][0-9]-[0-9]{2}-[0-9]{2}-[0-9]{2}-[0-9]{2}'
./script_exo2.sh: line 7: syntax error near unexpected token `|'
./script_exo2.sh: line 7: `t3=( \+ | 00 )33[1-9][0-9].[0-9]{2}.[0-9]{2}.[0-9]{2}.[0-9]{2}'
./script_exo2.sh: line 9: syntax error near unexpected token `('
./script_exo2.sh: line 9: `grep -e $1 ( $t | $t2 | $t3 )'

Your t2 and t3 have one more digit than the samples you're trying to match. Also, you need to quote the arguments, and get rid of those spaces:
#!/bin/sh
t='(\+|00)33[1-9][0-9]{8}'
t2='(\+|00)33[1-9]-[0-9]{2}-[0-9]{2}-[0-9]{2}-[0-9]{2}'
t3='(\+|00)33[1-9]\.[0-9]{2}\.[0-9]{2}\.[0-9]{2}\.[0-9]{2}'
exec grep -E -e "$t|$t2|$t3" "$#"
I've used sh instead of bash, as we're not using any Bash features not available in standard POSIX shells (e.g. dash).
I've used single quotes above for the definitions of t, t1 and t2, and double quotes where they are to be substituted.
I've told grep to understand Extended Regular Expressions via the -E flag, and I've put the pattern as the argument to the -e ("expression") flag to grep.
The grep process execs in place of the shell, as there is no reason to fork for it.
I've passed the full set of input arguments "$#" so you can give extra options to grep (such as -w, -n or -o, for example), and choose whether to supply a file or stream stdin to your script.
Note also that if you're willing to accept a mix of ., - or nothing separating digit pairs, you can simplify your three expressions to just one:
(\+|00)33[1-9][0-9]([-.]?[0-9]{2}){4}
and the script becomes
#!/bin/bash
exec grep -E -e '(\+|00)33[1-9]([-.]?[0-9]{2}){4}' "$#"
If you need the delimiters to match, then you can use a capture group for that:
#!/bin/bash
exec grep -E -e '(\+|00)33[1-9]([-.]?)[0-9]{2}(\2[0-9]{2}){3}' "$#"

Related

Bash Square Bracket Expansion

So the official bash manual states that "For example, the regular expression ‘[0123456789]’ matches any single digit, whereas ‘[^()]’ matches any single character that is not an opening or closing parenthesis,", copied a link at the bottom of this question, for context.
So I tested it every which way I could think of, to try and do the "negate" part of this, but I could not get it to work:
$ cat test
a line
b line
c line
d line
$ grep [^abc] test
a line
b line
c line
d line
$ grep '[^abc]' test
a line
b line
c line
d line
$ grep '[^(abc)]' test
a line
b line
c line
d line
[$ grep [^(abc)] test
bash: syntax error near unexpected token `('
https://www.gnu.org/software/grep/manual/html_node/Character-Classes-and-Bracket-Expressions.html
I was expecting just line D to be shown
[^abc] matches any character which is not a, b, or c.
It matches a line because it includes , l, i, n, and e, none of which are the excluded characters.
To ensure that no character in the string matches any in the list you would need grep '^[^abc]*$' test
To stick with the bash tag on the question, here’s a pure Bash option that takes advantage of extglob (which is mostly enabled by default).
while IFS= read -r line; do
[[ "$line" = *([^abc]) ]] && printf '%s\n' "$line"
done < test

Bash merge file lines

I have a file that holds output from a test.
test 1
42
test 2
69
test 3
420
test 4
55378008
I would like to make the test output appear on the same line as the test name. like so:
test 1: 42
test 2: 69
test 3: 420
test 4: 55378008
I am sure there is some fancy sed, awk or perl way to do this but I am stuck.
And here is another one in sed flavor to complete the offer :
sed 'N ; s/\n/: /' input_file
For each (odd) line starting from the first, append the next (even) one in pattern space separated by a LF, then just replace this LF by :.
awk 'FNR%2{printf "%s: ", $0; next}1' file
This prints odd lines with suffix : and without newline and even lines with a newline.
pr has this built-in, but if you need whitespace adjustment as well, then sed/awk/perl solutions suggested in other answers will suit you better
$ pr -2ats': ' ip.txt
test 1: 42
test 2: 69
test 3: 420
test 4: 55378008
This combines 2 lines at a time with : as the separator.
Just replace the line feed of odd lines with :␠.
perl -pe's/\n/: / if $. % 2'
You have mentioned that you want to removing leading and trailing whitespace as well. For that, you can use the following:
perl -pe's/^\h+|\h+$/g; s/\n/: / if $. % 2'
Specifying file to process to Perl one-liner
A shell solution, which is very slow on large set of data/files.
while IFS= read -r odd_line; do
IFS= read -r even_line
printf '%s: %s\n' "$odd_line" "$even_line"
done < file.txt
On the other hand if the colon is not a requirement paste can do the job.
paste - - < file.txt
Bash solution
skips empty lines
process both UNIX/DOS format 'end of line'
accepts filename as argument or otherwise reads data from STDIN
#!/bin/bash
while read p1
do
[[ -z $p1 ]] && continue
# p1=`echo -n $p1 | tr -d "\r"` # replaced with following line
p1=${p1//$'\r'/}
read p2
echo -n "$p1: $p2"
done < ${1:-/dev/stdin}
Output
test 1: 42
test 2: 69
test 3: 420
test 4: 55378008
NOTE: no empty lines allowed between lines for join

Unable to define line number in sed by variable

I'm trying to use an array to define the lines to replace using sed; I can delete the lines using a variable for the line number but I can't get sed to use the variable to define the line number to write to. The problem seems to reside in the insert line. How do you pass the value of an array as a line number to sed?
#!/bin/bash
lineNum=$(sed -n '/max_allowed_packet/=' /etc/mysql/my.cnf)
IFS= #There's a space as the delimiter#
ary=($lineNum)
#for key in "${!ary[#]}";
# do
# sed -i '$ary[$key]'d /etc/mysql/my.cnf;
# #The folllowing line errors#
# sed -i "'$ary[$key]'imax_allowed_packet = 32M" /etc/mysql/my.cnf;
# #The above line errors#
#done
#for val in "${ary[#]}";
# do
# sed -i "${val}d" /etc/mysql/my.cnf;
# sed -i "${val}imax_allowed_packet = 32M" /etc/mysql/my.cnf;
# done
for val in "${ary[#]}";
do
sed -i "${val}s/.*/imax_allowed_packet = 32M" /etc/mysql/my.cnf";
done
For the first stanza of script I get the following output:
Error: sed: -e expression #1, char 1: unknown command: `''
For the second Stanza I get the following output:
sed: -e expression #1, char 3: unknown command:
'
sed: -e expression #1, char 3: unknown command:
'
For the third Stanza I get the following output:
./test.sh: line 22: unexpected EOF while looking for matching `"'
./test.sh: line 24: syntax error: unexpected end of file
Edit, rewriting the sed commands as sed -i "${ary[$key]}" generates the following error output: sed: -e expression #1, char 3: unknown command: `
I think you're over-complicating the issue. Your script can be reduced to this:
sed 's/\(max_allowed_packet\).*/\1 = 32M/' /etc/mysql.cnf
This performs a substitution on every occurrence of max_allowed_packet, setting the rest of the line to = 32M. Add the -i switch to overwrite the file when you're happy with the result.
Problems with your attempt
Shell parameters are not expanded within single quotes, so you would need to use double quotes, e.g. sed -i "${ary[$key]}d". You can use set -x to see what is happening here - at the moment, you will see the literal string $ary[$key], rather than the array value.
If I understand your intention correctly (you want to substitute the entire line), there's no need to call sed twice:
for val in "${ary[#]}"; do
sed -i.bak "${val}s/.*/imax_allowed_packet = 32M" /etc/mysql/my.cnf
done
I have chosen to loop through the values of the array, instead of the keys, in order to simplify things a little. When using the -i option, it is always a good idea to specify a backup file, as I have done.

Net::OpenSSH command remote with multi pipeline

i have a problem when attempt run multi command in remote linux, with Perl and Module Net::OpenSSH. .
use Net::OpenSSH;
my $new_connect = Net::OpenSSH->new($D_CONNECT{'HOST'}, %OPT);
my $file = "file.log.gz"
my ($output2, $error2) = $new_connect->capture({ timeout => 10 }, "gunzip -c /path/to/file/$file | tail -n1 | awk '/successfully completed/ {print \$NF}'");
the output that i get is:
bash: -c: line 1: syntax error near unexpected token |'
bash: -c: line 1: |tail -n1 |awk '/successfully completed/ {print $NF}''
;;;
any idea or suggestion, thanks.
Fcs
Probably a quoting error. Just let Net::OpenSSH take care of the quoting for you:
my ($output2, $error2) = $new_connect->capture({ timeout => 10 },
'gunzip', '-c', "/path/to/file/$file", \\'|',
'tail', '-n1', \\'|',
'awk', '/successfully completed/ {print $NF}');
Note how the pipes (|) are passed as a double reference so that they are passed unquoted to the remote shell. The module documentation has a section on quoting.
That looks like the error message you'd get if you had a newline at the end of your $file string, causing the pipe character to be at the start of a second line (interpreted as the start of a second command).
This test deomstrates the same error:
bash -c 'echo foo
| cat'
So I guess your bug doesn't really occur with $file = "file.log.gz" and whatever your real $file is, you need to chomp it.
A bigger mystery is why bash says the error is on line 1 of the -c. ash, ksh, and zsh all correctly report it on line 2.

unexpected EOF while looking for matching `'' while using sed

Yes this question has been asked many times, and in the answer it is said to us \ escape character before the single quote.
In the below code it isn't working:
LIST="(96634,IV14075295,TR14075685')"
LIST=`echo $LIST | sed 's/,/AAA/g' `
echo $LIST # Output: (96634AAAIV14075295AAATR14075685')
# Now i want to quote the list elements
LIST=`echo $LIST | sed 's/,/\',\'/g' ` # Giving error
# exit 0
Error :
line 7: unexpected EOF while looking for matching `''
line 8: syntax error: unexpected end of file
Instead of single quotes, use double quotes in sed command, and also remove the space before last backtick. If there is single quote present in the sed pattern then use an alternative enclosing quotes(ie, double quotes),
sed "s/,/\',\'/g"
And the line would be,
LIST=$(echo $LIST | sed "s/,/\',\'/g")
Don't use backticks inside the sripts instead of bacticks, use $()
You can use awk
echo $LIST
(96634,IV14075295,TR14075685')
LIST=$(awk '{gsub(/,/,q"&"q)};gsub(/\(/,"&"q)1' q="'" <<< $LIST)
echo $LIST
('96634','IV14075295','TR14075685')
To prevent problems with the single quote, I just set it to an awk variable.
consider
LIST="$(sed "s/,/\',\'/g" <<< "$LIST")"
but first and last elements probably won't get quoted completely, because of missing leading and trailing comma
btw, you don't need to subshell to sed - string matching and substitution is entirely within the capabilities of bash:
LIST="${LIST//,/\',\'}"

Resources