How can I titleize text (capitalize each word) with jq?
Expected transformation: "lorum ipsum" → "Lorum Ipsum"
Given a string you consider calling a word, you may uppercase its first character by converting it into an array (./""), modifying the first element (first|=ascii_upcase) and joining all elements back together (add) like so
./"" | first |= ascii_upcase | add
Similarly, given a string of words, split it into an array of words, apply the above to each element using map and join them back together. Depending on what you (or the input data requires you to) consider a word, splitting the string might differ. Given your input example ("lorum ipsum"), using a single space character as word separator will suffice. I'll set it in a variable (--arg ws ' ') and use it for splitting (./$ws) and joining (join($ws)):
echo '"lorum ipsum"' | jq --arg ws ' ' '
./$ws | map(
./"" | first |= ascii_upcase | add
) | join($ws)
'
this may be achieved with gsub, ascii_upcase, and named capture groups:
$ echo '"lorum ipsum"' | jq 'gsub("(?<x>[A-z])(?<y>[A-z]+)"; "\(.x|ascii_upcase)\(.y)")'
"Lorum Ipsum"
You can use nawk for loop, toupper, sub and substr as below:
echo 'lorum ipsum' | nawk '{for(i=1;i<=NF;i++)sub(/./,toupper(substr($i,1,1)),$i)}1'
Related
I want to generate a comma separated ip values with mapped ports and create a string.
Here is my code:
zk_ip="['192.168.0.10', '192.168.0.20', '192.168.0.30']"
zk_host=""
for i in $zk_ip[#]
do
add=$(echo "$i:2181")
zk_host="$zk_host $add"
done
echo $zk_host
Output:
[192.168.0.10,:2181 192.168.0.20, :2181 192.168.0.30]:2181
Expected ouptut:
192.168.0.10:2181, 192.168.0.20:2181, 192.168.0.30:2181
So, you have a JSON-ish array that you want to modify (JSON strings are enclosed in double quotes).
I would use a JSON parser to manage this: jq
zk_ip="['192.168.0.10', '192.168.0.20', '192.168.0.30']"
new_ip=$(echo "$zk_ip" | tr "'" '"' | jq -c 'map("\(.):2181")')
echo "$new_ip"
["192.168.0.10:2181","192.168.0.20:2181","192.168.0.30:2181"]
If you want the output to not look like JSON, you can do:
new_ip=$(echo "$zk_ip" | tr "'" '"' | jq -r 'map("\(.):2181") | join(", ")')
echo "$new_ip"
192.168.0.10:2181, 192.168.0.20:2181, 192.168.0.30:2181
You may use:
zk_ip="['192.168.0.10', '192.168.0.20', '192.168.0.30']"
zk_host=""
for i in ${zk_ip//[][,\']/}; do
zk_host+="$i:2181, "
done
echo "${zk_host%, }"
192.168.0.10:2181, 192.168.0.20:2181, 192.168.0.30:2181
Assuming that you have your IP addresses in an array, such as
zk_ip=( '192.168.0.10' '192.168.0.20' '192.168.0.30' )
then,
( IFS=','; printf '%s\n' "${zk_ip[*]/%/:2181}" )
would print
192.168.0.10:2181,192.168.0.20:2181,192.168.0.30:2181
Setting IFS makes "${zk_ip[*]}" expand to a comma-delimited string with all the entries of the array. With /%/:2181 each element is suffixed with the string :2181 before printing.
I have small task.
I should write:
data="duke,rock,hulk,donovan,john"
And in the next variable, i should change delimiter of first variable.
data2="duke|rock|hulk|donovan|john"
What is the correct way to do this on bash ?
This is a small part of script, what i should do.
For example, i use construction "WHILE-GETOPS-CASE" to use usernames in parameter for excluding them.
ls /home/ | egrep -v $data2
You can easily replace a single character with an expansion:
data="duke,rock,hulk,donovan,john"
data2=${data//,/|}
echo "$data2"
Breaking down the syntax:
${data means "expand based on the value found in variable data;
// means "search all occurences of";
The lone / means "replace with what follows".
Note that some characters may need to be escaped, but not the comma and vertical bar.
Then you may filter the results like this:
ls /home/ | egrep -v "$data2"
Another very similar way would be to use tr (translate or delete characters):
data="duke,rock,hulk,donovan,john"
data2=$(echo $data | tr ',' '|')
echo "$data2"
I'm reading from a file and want to
Get the text for value where key = "key".
i.e.
key = value
Trim any white space around the value or the key
However, value can also contain an = because it's a base64 encoded field.
I've been using this previously:
key=`egrep 'key' myfile | cut -f2 -d'=' | sed 's/ //g'`
But, cut works globally. Is there a way of making it work on just the first item?
Or perhaps there more efficient way of doing this.
e.g. if myfile = ceph.client.keyring
[client.admin]
key = AQAa6HRVaDKxLxAANulnamD/5x2SBly7kPPatg==
auid = 0
caps mds = "allow"
caps mon = "allow *"
caps osd = "allow *"
I'm wanting to read into a variable the value for key (AQAa6HRVaDKxLxAANulnamD/5x2SBly7kPPatg==)
Using sed
Your your example myfile:
$ sed -rn '/^[[:space:]]*key[ =]/ s/[^=]*=[[:space:]]*//p' myfile
AQAa6HRVaDKxLxAANulnamD/5x2SBly7kPPatg==
How it works:
sed -rn
This tells sed to use extended regular expressions, -r, and not to print unless we explicitly ask it to, -n.
/^[[:space:]]*key[ =]/
This selects lines that optionally start with whitespace, followed by key, followed by either a blank or an equal sign, =.
s/[^=]*=[[:space:]]*//p
For those selected lines, this strips out everything before the first equal sign and any whitespace after the equal sign. The p option tells sed to print the resulting line.
Using awk
This assumes that, as in your example, the first equal sign is surrounded by spaces and that the value for key contains no whitespace:
$ awk '$1=="key"{print $3}' myfile
AQAa6HRVaDKxLxAANulnamD/5x2SBly7kPPatg==
How it works:
$1=="key"
This selects lines whose first field is key.
{print $3}
For those selected lines, this prints the third field.
Alternate sed solution
Doing the selecting and substituting in one step:
$ sed -rn 's/^[[:space:]]*key[[:space:]]=[[:space:]]*//p' myfile
AQAa6HRVaDKxLxAANulnamD/5x2SBly7kPPatg==
Using grep with Perl regex:
grep -oP "(?<=\skey\s=\s)[^\s]*" file
Output:
AQAa6HRVaDKxLxAANulnamD/5x2SBly7kPPatg==
Note: It assumes there's only one white space between key and =, & = and value
Or using sed:
sed -nr 's/.*\skey\s*=\s*([^\s]*).*/\1/p' file
AQAa6HRVaDKxLxAANulnamD/5x2SBly7kPPatg==
I have the following input csv file:
"aaa","1","xxx"
"ccc, Inc.","6100","yyy"
"bbb","609","zzz"
I wish to sort by the second column as numbers,
I tried
sort --field-separator=',' --key=2n
the problem is that since all values are quoted, they don't get sorted correctly by -n (numeric) option. is there a solution?
A little trick, which uses a double quote as the separator:
sort --field-separator='"' --key=4 -n
For a quoted csv use a language that has a proper csv parser. Here is an example using perl.
perl -MText::ParseWords -lne '
chomp;
push #line, [ parse_line(",", 0, $_) ];
}{
#line = sort { $a->[1] <=> $b->[1] } #line;
for (#line) {
local $" = qw(",");
print qq("#$_");
}
' file
Output:
"aaa","1","xxx"
"bbb","609","zzz"
"ccc, Inc.","6100","yyy"
Explanation:
Remove the new line from input using chomp function.
Using a code module Text::Parsewords parse the quoted line and store it in an array of array without the quotes.
In the END block, sort the array of array on second column and assign it to the original array of array.
For every item in our array of array, we set the output list separator to "," and we print it with preceding and trailing " to create the lines in original format.
Dropping your example into a file called sort2.txt I found the following to work well.
sort -t'"' -k4n sort2.txt
Using sort with the following commands (thank you for the refinements Jonathan)
-t[optional single character separator other than tab. Defined within the single quotes]'"'.
-k4 choose the value in the fourth key.(k)delimited by ", and on the 4th key value
-n numeric sort
file name avoid the use of chaining as unnecessary
Hope this helps!
There isn't going to be a really simple solution. If you make some reasonable assumptions, then you could consider:
sed 's/","/^A/g' input.csv |
sort -t'^A' -k 2n |
sed 's/^A/","/g'
This replaces the "," sequence with Control-A (shown as ^A in the code), then uses that as the field delimiter in sort (the numeric sort on column 2), and then replace the Control-A characters with "," again.
If you use bash, you can use the ANSI C quoting mechanism $'\1' to embed the control characters visibly into the script; you just have to finish the single-quoted string before the escape, and restart it afterwards:
sed 's/","/'$'\1''/g' input.csv |
sort -t$'\1' -k 2n |
sed 's/'$'\1''/","/g'
Or play with double quotes instead of single quotes, but that gets messy because of the double quotes that you are replacing. But you can simply type the characters verbatim and editors like vim will be happy to show them to you.
Sometimes the values in the CSV file are optionally quoted, only when necessary. In this case, using " as a separator is not reliable.
Example:
"Forest fruits",198
Apples,456
bananas,67
Using awk, sort and cut, you can sort the original file, here by the first column :
awk -F',' '{
a = $1; # or the column index you want
gsub(/(^"|"$)/, "", a);
print a","$0
}' file.csv | sort -k1 | cut -d',' -f1 --complement
This will bring the column you want to sort on in front without quotes, then sort it the way you want, and remove this column at the end.
I need pass a series of couples values which are arguments for a c++ software. So I wrote this script:
while read randomNumbers; do
lambda = $randomNumbers | cut -f1 -d ' '
mi = $randomNumbers | cut -f2 -d ' '
./queueSim mm1-queue $lambda $mi
done < "randomNumbers"
where the first arg is the first value for each line in the file "randomNumbers" and the second one in the second value (of course). I got a segfault and a "command not found".
How can I assign to lambda and mi valus got from the line and pass this variable to c++ software?
There's no need for cut. Let read split the line for you:
while read lambda mi; do
./queueSim mm1-queue $lambda $mi
done < randomNumbers
Note that it is also commonly used in conjunction with IFS to split the input line on different fields. For example, to parse /etc/passwd ( a file with colon separated lines ), you will often see:
while IFS=: read username passwd uid gid info home shell; do ...
I would recommend assigning the values like this:
lambda=$(echo $randomNumbers | cut -f1 -d ' ')
mi=$(echo $randomNumbers | cut -f2 -d ' ')
the way you do it, you actually try to run a command that is named like whatever is the current content of $randomNumbers.
Edit:
Another thing: since your columns are delimited by a whitespace character, you could also just read the entire line into an array whose elements are separated by whitespaces as well. One way to achieve this is:
columns=( $(echo "$randomNumbers" | grep -o "[^ ]*") )
./queueSim mm1-queue ${columns[#]::2}
The first line matches all substrings that are not containing any spaces separately and puts them into the array columns. The second line does the same thing as the corresponding one in your implementation: inserting the first two columns as parameters. Since is done with slicing: you take the entire array ${columns[#]}, but select a certain subsequence of it by applying the boundary ::2 on the right, which returns in every element of columns beginning from the left (position 0), that is not on a position >=2.