Can we having optional arguments in tcsh alias? - arguments

I have the following alias
alias ackalias "acknoredirect !:2-$ '^[\sun]alias.?(!:1)' ~/.alias"
The problem is that it doesn't work if I do ackalias tmux; it always needs 2 or more arguments. For example, the following works great: ackalias tmux -A 2 -B 2
Is it possible to replace !:2-$ in the alias definition with something that says that 2nd and more arguments are optional?
Update
#Mark As you suggested, I tried out the below simple example and found issues when I have 2 or more optional arguments. Check the below example:
alias test2 'echo \!:1* \!:2* \!:3* \!:4*'
test2 a
test2 a b
test2 a b c
test2 a b c d
test2 a b c d e
The output was:
a
a b b
a b c b c c
a b c d b c d c d d
a b c d e b c d e c d e d e
I might need to add if conditions but it should eventually work. Thanks.

try
alias ackalias "acknoredirect \!:2* '^[\sun]alias.?(\!:1)' ~/.alias"
(note I also added escapes before the !'s)

Finally this worked!
alias test4 'set arg1 = `echo \!:1* | awk '"'"'{ print $1 }'"'"'`; \\
echo -n "Arg num 1 = $arg1 "; \\
set arg2 = `echo \!:2* | awk '"'"'{ print $1 }'"'"'`; \\
echo -n "Arg num 2 = $arg2 "; \\
set arg3 = `echo \!:3* | awk '"'"'{ print $1 }'"'"'`; \\
echo -n "Arg num 3 = $arg3 "; \\
set arg4 = `echo \!:4* | awk '"'"'{ print $1 }'"'"'`; \\
echo -n "Arg num 4 = $arg4 "; \\
set arg5 = `echo \!:5* | awk '"'"'{ print $1 }'"'"'`; \\
echo -n "Arg num 5 = $arg5 "; \\
echo ""; \\
'
test4 abc
test4 abc def ghi
test4 abc def ghi jkl
test4 abc def ghi jkl mno
test4 abc def ghi jkl mno pqr
I soon need to start converting to zsh..

Related

How to count occurrences of a phrase in Bash?

I have an array:
ABC
GHI
XYZ
ABC
GHI
DEF
MNO
XYZ
How can I count the occurrences of each phrase in this array?
(Can I use a for loop?)
Expected output:
2 ABC
1 DEF
2 GHI
1 MNO
2 XYZ
Thank you so much!
sort file.txt | uniq -c should do the job.
If you mean an array in bash, echo them:
array=(ABC GHI XYZ ABC GHI DEF MNO XYZ)
for i in "${array[#]}"; do echo "$i"; done | sort | uniq -c
Output:
2 ABC
1 DEF
2 GHI
1 MNO
2 XYZ
Using pure bash and an associative array to hold the counts:
#!/usr/bin/env bash
declare -a words=(ABC GHI XYZ ABC GHI DEF MNO XYZ) # regular array
declare -A counts # associative array
# Count how many times each element of words appears
for word in "${words[#]}"; do
counts[$word]=$(( ${counts[$word]:-0} + 1 ))
done
# Order of output will vary
for word in "${!counts[#]}"; do
printf "%d %s\n" "${counts[$word]}" "$word"
done

how to find the last grouped digit in a string in bash

This is a follow-up question to this question, regarding how to know the number of grouped digits in string.
In bash,
How can I find the last occurrence of a group of digits in a string?
So, if I have
string="123 abc 456"
I would get
456
And if I had
string="123 123 456"
I would still get
456
Without external utilities (such as sed, awk, ...):
$ s="123 abc 456"
$ [[ $s =~ ([0-9]+)[^0-9]*$ ]] && echo "${BASH_REMATCH[1]}"
456
BASH_REMATCH is a special array where the matches from [[ ... =~ ... ]] are assigned to.
Test code:
str=("123 abc 456" "123 123 456" "123 456 abc def" "123 abc" "abc 123" "123abc456def")
for s in "${str[#]}"; do
[[ $s =~ ([0-9]+)[^0-9]*$ ]] && echo "$s -> ${BASH_REMATCH[1]}"
done
Output:
123 abc 456 -> 456
123 123 456 -> 456
123 456 abc def -> 456
123 abc -> 123
abc 123 -> 123
123abc456def -> 456
You can use a regex in Bash:
$ echo "$string"
123 abc 456
$ [[ $string =~ (^.*[ ]+|^)([[:digit:]]+) ]] && echo "${BASH_REMATCH[2]}"
456
If you want to capture undelimited strings like 456 or abc123def456 you can do:
$ echo "$string"
test456text
$ [[ $string =~ ([[:digit:]]+)[^[:digit:]]*$ ]] && echo "${BASH_REMATCH[1]}"
456
But if you are going to use an external tool, use awk.
Here is a demo of Bash vs Awk to get the last field of digits in a string. These are for digits with ' ' delimiters or at the end or start of a string.
Given:
$ cat file
456
123 abc 456
123 123 456
abc 456
456 abc
123 456 foo bar
abc123def456
Here is a test script:
while IFS= read -r line || [[ -n $line ]]; do
bv=""
av=""
[[ $line =~ (^.*[ ]+|^)([[:digit:]]+) ]] && bv="${BASH_REMATCH[2]}"
av=$(awk '{for (i=1;i<=NF;i++) if (match($i, /^[[:digit:]]+$/)) last=$i; print last}' <<< "$line")
printf "line=%22s bash=\"%s\" awk=\"%s\"\n" "\"$line\"" "$bv" "$av"
done <file
Prints:
line= "456" bash="456" awk="456"
line= "123 abc 456" bash="456" awk="456"
line= "123 123 456" bash="456" awk="456"
line= "abc 456" bash="456" awk="456"
line= "456 abc" bash="456" awk="456"
line= "123 456 foo bar" bash="456" awk="456"
line= "abc123def456" bash="" awk=""
grep -o '[0-9]\+' file|tail -1
grep -o lists matched text only
tail -1 output only the last match
well, if you have string:
grep -o '[0-9]\+' <<< '123 foo 456 bar' |tail -1
You may use this sed to extract last number in a line:
sed -E 's/(.*[^0-9]|^)([0-9]+).*/\2/'
Examples:
sed -E 's/(.*[^0-9]|^)([0-9]+).*/\2/' <<< '123 abc 456'
456
sed -E 's/(.*[^0-9]|^)([0-9]+).*/\2/' <<< '123 456 foo bar'
456
sed -E 's/(.*[^0-9]|^)([0-9]+).*/\2/' <<< '123 123 456'
456
sed -E 's/(.*[^0-9]|^)([0-9]+).*/\2/' <<< '123 x'
123
RegEx Details:
(.*[^0-9]|^): Match 0 or more characters at start followed by a non-digit OR line start.
([0-9]+): Match 1+ digits and capture in group #2
.*: Match remaining characters till end of line
\2: Replace it with back-reference #2 (what we captured in group #2)
Another way to do it with pure Bash:
shopt -s extglob # enable extended globbing - for *(...)
tmp=${string%%*([^0-9])} # remove non-digits at the end
last_digits=${tmp##*[^0-9]} # remove everything up to the last non-digit
printf '%s\n' "$last_digits"
This is a good job for parameter expansion:
$ string="123 abc 456"
$ echo ${string##* }
456
A simple answer with gawk:
echo "$string" | gawk -v RS=" " '/^[[:digit:]]+$/ { N = $0 } ; END { print N }'
With RS=" ", we read each field as a separate record.
Then we keep the last number found and print it.
$ string="123 abc 456 abc"
$ echo "$string" | gawk -v RS=" " '/^[[:digit:]]+$/ { N = $0 } ; END { print N }'
456

Bash: extract column using empty lines as separators

I have a file like:
1
2
3
4
5
a
b
c
d
e
And want to put it like:
1 a
2 b
3 c
4 d
5 e
Is there a quick way to do it in bash?
pr is the tool to use for columnizing data:
pr -s" " -T -2 filename
With paste and process substitution:
$ paste -d " " <(sed -n '1,/^$/{/^$/d;p}' file) <(sed -n '/^$/,${//!p}' file)
1 a
2 b
3 c
4 d
5 e
Simple bash script the does the job:
nums=()
is_line=0
cat ${1} | while read line
do
if [[ ${line} == '' ]]
then
is_line=1
else
if [[ ${is_line} == 0 ]]
then
nums=("${nums[#]}" "${line}")
else
echo ${nums[0]} ${line}
nums=(${nums[*]:1})
fi
fi
done
Run it like this: ./script filename
Example:
$ ./script filein
1 a
2 b
3 c
4 d
5 e
$ rs 2 5 <file | rs -T
1 a
2 b
3 c
4 d
5 e
If you want that extra separator space off, use -g1 in the latter rs. Explained:
print file in 5 cols and 2 rows
-T transpose it

why read command reads all words into first name

My script:
#!/bin/bash
IFS=','
read a b c d e f g <<< $(echo "1,2,3,4,5,6,7") # <- this could be any other commands, I am just making up a dummy command call
echo $a
echo $b
echo $c
I expected it to output
1
2
3
But instead it outputs:
1 2 3 4 5 6 7
blank line
blank line
What did I do wrong?
You should use it like this:
IFS=, read a b c d e f g <<< "1,2,3,4,5,6,7"
Use IFS in same line as read to avoid cluttering the current shell environment.
And avoid using command substitution just to capture the output of a single echo command.
If you want to use a command's output in read then better use process substitution in bash:
IFS=, read a b c d e f g < <(echo "1,2,3,4,5,6,7")
This works:
#!/bin/bash
IFS=','
read a b c d e f g <<< "$(echo "1,2,3,4,5,6,7")"
echo $a; echo $b; echo $c
Note the quoting: "$( ...)". Without it, the string is split and becomes
$(echo "1,2,3,4,5,6,7") ===> 1 2 3 4 5 6 7
Giving 1 2 3 4 5 6 7 to read produces no splitting, as the IFS is ,.
Of course, this also works (IFS only apply to the executed command: read):
#!/bin/bash
IFS=',' read a b c d e f g <<< "$(echo "1,2,3,4,5,6,7")"
echo $a; echo $b; echo $c
And is even better like this:
#!/bin/bash
IFS=',' read a b c d e f g <<< "1,2,3,4,5,6,7"
echo $a; echo $b; echo $c
You do not need to "execute an echo" to get a variable, you already have it.
Technically, your code is correct. There is a bug in here-string handling in bash 4.3 and earlier that incorrectly applies word-splitting to the unquoted expansion of the command substitution. The following would work around the bug:
# Quote the expansion to prevent bash from splitting the expansion
# to 1 2 3 4 5 6 7
$ read a b c d e f g <<< "$(echo "1,2,3,4,5,6,7")"
as would
# A regular string is not split
$ read a b c d e f g <<< 1,2,3,4,5,6,7
In bash 4.4, this seems to be fixed:
$ echo $BASH_VERSION
4.4.0(1)-beta
$ IFS=,
$ read a b c d e f g <<< $(echo "1,2,3,4,5,6,7")
$ echo $a
1

Unset IFS value for shell script

In my use case I would like to change the value of IFS to a known separator (-). I tried the following:
OLDIFS=$IFS
IFS='-'
for x in $*
do
echo $x
done
IFS=$OLDIFS
When using e.g. -a b -c d as input string I expect the output to be
a b
c d
However, what I get is
a
b
c
d
I'm on AIX.
I tried your code and I get
a b
c d
Try this
$ cat >a <<.
#!/bin/sh
OLDIFS=$IFS
IFS='-'
for x in $*
do
echo $x
done
IFS=$OLDIFS
.
$ chmod +x a
$ ./a "-a b -c d"
a b
c d
$
Here is one way of getting this output using awk and avoid all the IFS manipulation:
s='-a b -c d'
echo "$s" | awk -F ' *- *' '{print $2 RS $3}'
a b
c d

Resources