How to save the result of grep to variable x? - bash

#!/bin/bash
for i in *.pdf; do
echo $i
x= pdfinfo "$i" | grep "Title" # nothing is stored in variable x
echo $x
if [ ! -z $x ]; then
echo $x # print null
cp $i "$x"
fi
done
Nothing is stored in variable x — but why not and how do I do it.

Add parentheses or backquotes:
x=$(pdfinfo "$i" | grep "Title")
or
x=`pdfinfo "$i" | grep "Title"`
Note that the latter solution should be avoided now, it is the historical way of doing it and is replaced by $(...). The $(...) solution is more readable in particular in case of nested substitutions.

You could use:
x=`pdfinfo "$i" | grep "Title"`

Related

Convert folder and file names to camel case

I have a list of folders and files whose names contain spaces. How can I change the names into camel case?
for oldname in *
do
newname=`echo $oldname | sed -e 's/ /_/g'`
if [ "$newname" = "$oldname" ]
then
continue
fi
if [ -e "$newname" ]
then
echo Skipping "$oldname", because "$newname" exists
else
mv "$oldname" "$newname"
fi
done
I have found this but it changes the spaces into underscores.
Try this Shellcheck-clean Bash code:
#! /bin/bash -p
lowers=abcdefghijklmnopqrstuvwxyz
uppers=ABCDEFGHIJKLMNOPQRSTUVWXYZ
for oldname in *; do
[[ $oldname == *[[:space:]]* ]] || continue
read -r -d '' -a parts <<<"$oldname"
newname=''
for p in "${parts[#]}"; do
char1=${p:0:1}
if [[ $lowers == *"$char1"* ]]; then
tmp=${lowers%"$char1"*}
uchar1=${uppers:${#tmp}:1}
newname+=${uchar1}${p:1}
else
newname+=$p
fi
done
if [[ -e $newname ]]; then
printf "Skipping '%s', because '%s' exists\\n" "$oldname" "$newname" >&2
else
echo mv -v -- "$oldname" "$newname"
fi
done
The code is intended to work with (the now ancient) Bash 3 because my understanding is that that is still the current version of the standard Bash on macOS. The code for uppercasing the first letter of filename parts is much more complicated than it would be with later versions of Bash (which have built-in mechanisms for case conversion). See How to convert a string to lower case in Bash? for information about changing case in various ways in various versions of Bash.
The code just prints the mv command that would be run. Remove the echo to make it actually do the mv.
See the accepted, and excellent, answer to Why is printf better than echo? for an explanation of why I replaced echo with printf for the "Skipping" message.
For comparison, this is Bash 4+ code:
#! /bin/bash -p
for oldname in *; do
[[ $oldname == *[[:space:]]* ]] || continue
read -r -d '' -a parts <<<"$oldname"
newname=''
for p in "${parts[#]}"; do
newname+=${p^}
done
if [[ -e $newname ]]; then
printf "Skipping '%s', because '%s' exists\\n" "$oldname" "$newname" >&2
else
echo mv -v -- "$oldname" "$newname"
fi
done
You can use the regular expression aptitude to deal with upper and lower case translations, regarding your current local collation (LC_ALL, check with the locale command).
If your filename's "words" are separated with a space and are all in lower case, you can use a simple shell script like this :
#!/bin/sh
while read -r FILENAME ; do
NEWNAME="`echo \"${FILENAME}\" | sed 's/ *\([^ ]\)/\u\1/g'`"
if [ ! "${NEWNAME}" ] ; then
NEWNAME="${FILENAME}";
fi
if [ "${FILENAME}" = "${NEWNAME}" ]; then
printf "No change : %s\\n" "${FILENAME}" >&2;
else
if [ -e "${NEWNAME}" ] ; then
printf "Already changed : %s => %s\\n" "${FILENAME}" "${NEWNAME}" >&2;
else
echo "mv \"${FILENAME}\" \"${NEWNAME}\"";
fi
fi
done
Remove the echo on echo "mv \"${FILENAME}\" \"${NEWNAME}\""; to do the mv.
Note that it should work fine with accented letters or any unicode letter having lower and upper code.
The script takes the file list to operate from stdin, so to use it "as is", you can use something like the following examples :
find . -type 'f' | theScript.sh
For a whole tree of files.
For folders, you'll have to operate them separately. List them and sort them in a descending order.
ls -1 | theScript.sh
For files in the current folder.
If your files may have all or partial upper cases at start and you look to force them entirely to camel case, you can change the line :
NEWNAME="`echo \"${FILENAME}\" | sed 's/ *\([^ ]\)/\u\1/g'`"
With:
NEWNAME="\`echo \"${FILENAME}\" | sed 's/\(.*\)/\l\1/;s/ *\([^ ]\)/\u\1/g'\`"
If you have rename installed, then all you need to do is :
rename 's/ /_/g' *

If condition for "not equal" is not working as expected in shell script

#!/bin/bash
a=2
b=2
COUNTER=0
sam="abcd"
sam1="xyz"
sam2="mno"
for x in ls | grep .rpm
do
`C=rpm -qpR $x | grep -v CompressedFileNames | grep -v PayloadFilesHavePrefix | wc -l`
if [ "sam2"!="$sam1" ]
then
echo "${sam1}"
echo "${sam2}"
if [ $C -eq $a ]
then
COUNTER=$((COUNTER+1))
echo "${x}"
eval sam=$x
#eval sam1=sam | cut -d '-' -f 1
sam1=`echo "${sam}"| cut -d '-' -f 1`
if [ $COUNTER -eq $b ]
then
break
fi
fi
fi
sam2=`echo "${x}"| cut -d '-' -f 1`
done
This is the output I am getting:
xyz
mno
comps-4ES-0.20050107.x86_64.rpm
comps
comps
comps-4ES-0.20050525.x86_64.rpm
My question is: why is the if condition returning true despite sam1 and sam2 being equal? I have checked for non-equality.
Response is the same even if I use
if [ $C -eq $a ] && [ "$sam2" != " $sam1" ]
As Ansgar Wiechers pointed out, you're missing a "$" in front of the sam2 variable. That way, you're comparing the literal string "sam2" with the string value of $sam1 (which initially is set to "xyz"). What you want to do is compare the string values of both variables:
if [ "$sam2" != "$sam1" ]
Regarding $C, you should only include the commands to be evaluated inside backticks, not the evaluation itself. This is called a command substitution - a subshell is created in which the commands are executed, and the backtick expression is substituted by the computed value. The line should look like this:
C=`rpm -qpR $x | grep -v CompressedFileNames | grep -v PayloadFilesHavePrefix | wc -l`
Your for loop also needs a command substitution: for x in ls | grep .rpm makes it look as if you're piping the output of a for command into grep. What you want to do is iterate over the ls | grep part, which you can do with the following command substitution:
for x in `ls | grep .rpm`
Hi Guys Got the solution:
#!/bin/bash
read -p "enter dep number" a
read -p "enter no of rpms" b
COUNTER=0
sam="abcd"
sam1="xyz"
sam2="mno"
for x in `ls | grep .rpm`
do
C=`rpm -qpR $x |grep -v CompressedFileNames | grep -v PayloadFilesHavePrefix | wc -l`
# echo "${C}:c"
if [ $C -eq $a ] && [ "$sam2" != "$sam1" ]
then
COUNTER=$((COUNTER+1))
# echo "${COUNTER}:counter"
# echo "${x}"
eval sam=$x
#eval sam1=sam | cut -d '-' -f 1
sam1=`echo "${sam}"| cut -d '-' -f 1`
if [ $COUNTER -eq $b ]
then
break
fi
fi
sam2=`echo "${x}"| cut -d '-' -f 1`
#echo "${sam2}"
#echo "${sam1}"
done

Bash: Native way to check if an entry is one line?

I have a find script that automatically opens a file if just one file is found. The way I currently handle it is doing a word count on the number of lines of the search results. Is there an easier way to do this?
if [ "$( cat "$temp" | wc -l | xargs echo )" == "1" ]; then
edit `cat "$temp"`
fi
EDITED - here is the context of the whole script.
term="$1"
temp=".aafind.txt"
find src sql common -iname "*$term*" | grep -v 'src/.*lib' >> "$temp"
if [ ! -s "$temp" ]; then
echo "ø - including lib..." 1>&2
find src sql common -iname "*$term*" >> "$temp"
fi
if [ "$( cat "$temp" | wc -l | xargs echo )" == "1" ]; then
# just open it in an editor
edit `cat "$temp"`
else
# format output
term_regex=`echo "$term" | sed "s%\*%[^/]*%g" | sed "s%\?%[^/]%g" `
cat "$temp" | sed -E 's%//+%/%' | grep --color -E -i "$term_regex|$"
fi
rm "$temp"
Unless I'm misunderstanding, the variable $temp contains one or more filenames, one per line, and if there is only one filename it should be edited?
[ $(wc -l <<< "$temp") = "1" ] && edit "$temp"
If $temp is a file containing filenames:
[ $(wc -l < "$temp") = "1" ] && edit "$(cat "$temp")"
Several of the results here will read through an entire file, whereas one can stop and have an answer after one line and one character:
if { IFS='' read -r result && ! read -n 1 _; } <file; then
echo "Exactly one line: $result"
else
echo "Either no valid content at all, or more than one line"
fi
For safely reading from find, if you have GNU find and bash as your shell, replace <file with < <(find ...) in the above. Even better, in that case, is to use NUL-delimited names, such that filenames with newlines (yes, they're legal) don't trip you up:
if { IFS='' read -r -d '' result && ! read -r -d '' -n 1 _; } \
< <(find ... -print0); then
printf 'Exactly one file: %q\n' "$result"
else
echo "Either no results, or more than one"
fi
Well, given that you are storing these results in the file $temp this is a little easier:
[ "$( wc -l < $temp )" -eq 1 ] && edit "$( cat $temp )"
Instead of 'cat $temp' you can do '< $temp', but it might take away some readability if you are not very familiar with redirection 8)
If you want to test whether the file is empty or not, test -s does that.
if [ -s "$temp" ]; then
edit `cat "$temp"`
fi
(A non-empty file by definition contains at least one line. You should find that wc -l agrees.)
If you genuinely want a line count of exactly one, then yes, it can be simplified substantially;
if [ $( wc -l <"$temp" ) = 1 ]; then
edit `cat "$temp"`
fi
You can use arrays:
x=($(find . -type f))
[ "${#x[*]}" -eq 1 ] && echo "just one || echo "many"
But you might have problems in case of filenames with whitespace, etc.
Still, something like this would be a native way
no this is the way, though you're making it over-complicated:
if [ "`wc -l $temp | cut -d' ' -f1`" = "1" ]; then
edit "$temp";
fi
what's complicating it is:
useless use of cat,
unuseful use of xargs
and I'm not sure if you really want the editcat $temp`` which is editing the file at the content of $temp

simple string comparison in bash

I'm looking to get the number of subdirectories within a directory and compare that with a predefined number.
Ex:
cd /Applications/
x=echo ls -d */ | wc -l | sed 's/^ *//g'
y="52"
if [ "$x" == "$y" ]; then
echo "equal"
else
echo "not equal"
fi
It will give me a number for x (52 in my case), but will always say "not equal". What am I doing wrong? Any help would be greatly appreciated.
Thanks
For bash, do this:
cd /Applications/
dirs=( */ ) # dirs is an array with the names of directories here
if (( ${#dirs[#]} == $y )); then
echo "equal"
else
echo "not equal"
fi
Replace
x=echo ls -d */ | wc -l | sed 's/^ *//g'
with:
x="$(ls -d */ | wc -l | sed 's/^ *//g')"
Explanation: When you write x=echo ls -d */ ..., bash thinks that you mean to temporarily set the the variable x to a value of "echo" and then run the ls command. You, instead, want to run the command s -d */ | wc -l | sed 's/^ *//g' and save the output in x. The construct x=$(...) tells bash to execute whatever was in the parens and assign the output to x.
Also, bash's [ command does not accept ==. Use = for string comparison. However, you really want mathematical comparison. So use '-eq':
if [ "$x" -eq "$y" ]; then

Extract a certain part of a string in bash with different patterns

I have this file:
CLUSTERS=SP1,SP2,SP3
FNAME_SP1="REWARDS_BTS_SP1_<GTS>.dat"
FNAME_SP2="DUMP_LOG_SP2_<GTS>.dat"
FNAME_SP3="TEST_CASE_TABLE_SP3_<GTS>.dat"
What I want to get from these are:
REWARDS_BTS_SP1_
DUMP_LOG_SP2_
TEST_CASE_TABLE_SP3_
I loop through the CLUSTERS field, get the values, and use it to find the appropriate FNAME_<CLUSTERNAME> value. Basically, the CLUSTERS value are ALWAYS before the _<GTS> part of the string. Any string pattern will do, provided that the CLUSTERS value come before the _<GTS> at the end of the string.
Any suggestions? Here's a part of the script.
function loadClusters() {
for i in `echo ${!CLUSTER*}`
do
CLUSTER=`echo ${i} | grep $1`
if [[ -n ${CLUSTER} ]]; then
CLUSTER=${!i}
break;
fi
done
echo -e ${CLUSTER}
}
function loadClustersCampaign() {
for i in `echo ${!BPOINTS*}`
do
BPOINTS=`echo ${i} | grep $1`
if [[ -n ${BPOINTS} ]]; then
BPOINTS=${!i}
break;
fi
done
for i in `echo ${!FNAME*}`
do
FNAME=`echo ${i} | grep $1`
if [[ -n ${FNAME} ]]; then
FNAME=${!i}
break;
fi
done
echo -e ${BPOINTS}"|"${FNAME}
}
#get clusters
clusters=$(loadClusters $1)
for i in `echo $clusters | sed 's/,/ /g'`
do
file=$(loadClustersCampaign ${i/-/_} | awk -F"|" '{print $2}') ;
echo $file;
#then get the part of the $file variable
done
Fun with Shell Parameter Expansions
You can use matching-prefix notation and indirect expansion to get at the variables you want, and use the "remove suffix" expansion on each result to collect just the portions of the filename that you want. For example:
FNAME_SP1='REWARDS_BTS_SP1_<GTS>.dat'
FNAME_SP2='DUMP_LOG_SP2_<GTS>.dat'
FNAME_SP3='TEST_CASE_TABLE_SP3_<GTS>.dat'
for cluster in "${!FNAME_SP#}"; do
echo ${!cluster%%<GTS>*}
done
This will print out the following:
REWARDS_BTS_SP1_
DUMP_LOG_SP2_
TEST_CASE_TABLE_SP3_
but you could issue any valid shell command inside the loop instead of using echo.
See Also
http://www.gnu.org/software/bash/manual/html_node/Shell-Parameter-Expansion.html
If you like an awk solution for this ,may be below will be useful.
> echo 'FNAME_SP1="REWARDS_BTS_SP1_<GTS>.dat"' | awk -F"<GTS>" '{split($1,a,"=\"");print substr(a[2],2)}'
REWARDS_BTS_SP1_
Furthur more detail below:
> cat temp
LUSTERS=SP1,SP2,SP3
FNAME_SP1="REWARDS_BTS_SP1_<GTS>.dat"
FNAME_SP2="DUMP_LOG_SP2_<GTS>.dat"
FNAME_SP3="TEST_CASE_TABLE_SP3_<GTS>.dat"
> awk -F"<GTS>" '/FNAME_SP/{split($1,a,"=");print substr(a[2],2)}' temp
REWARDS_BTS_SP1_
DUMP_LOG_SP2_
TEST_CASE_TABLE_SP3_
>

Resources