I got .txt file which its content is
5742060626,Ms.Pimpan Tantivaravong,Female
5742065826,Ms.Kaotip Tanti,Female
-
I create an interface script to add list in this file
First, I have to compare the input id with the exitsting id in a list.
I use cut command to read only 1st column of .txt file.
But,I got a problem when I am trying to compare it.
Here is my code.
-
!/bin/bash
#
datafile='student-2603385.txt'
while read p;
do
if [ "$id" == (echo $p | cut -d, -f1) ]
then
echo 'duplicate id'
fi
done <$datafile
-
could anyone suggest me, how should I do?
Thank you
Your script has numerous quoting bugs, always quote variable expansion when the variable contains a file name, it is also expected when you want to avoid word splitting and pathname expansion by shell.
Letting that aside, in if [ "$id" == (echo $p | cut -d, -f1) ]:
You need command substitution, $() around echo ... | cut ..., not a subshell ()
you also need quotes around $() to prevent word splitting (and pathname expansion)
== is bash-ism, not defined by POSIX, just a reminder
try to use [[ as much as possible, being a shell keyword [[ handles word splitting
So with test ([):
if [ "$id" == "$(echo "$p" | cut -d, -f1)" ]
better:
if [[ $id == $(echo "$p" | cut -d, -f1) ]]
Related
This question already has answers here:
Why does adding spaces around bash comparison operator change the result?
(2 answers)
How to pass the value of a variable to the standard input of a command?
(9 answers)
Closed 1 year ago.
I want to check which lines of the file /etc/passwd end with the "/bin/bash" string (field number 7, ":" as delimiter).
So far, I've written the following code:
while read line
do
if [ $("$line" | cut -d : -f 7)=="/bin/bash" ]
then
echo $line | cut -d : -f 1
echo "\n"
fi
done < /etc/passwd
Currently, executing the script throws errors that show a bad interpretation (most likely due to the syntax).
I'd appreciate if you could help me.
You MUST surround the == operator with spaces. [ and [[ do different things based on how many arguments are given:
if [ "$( echo "$line" | cut -d: -f7 )" == "/bin/bash" ]; ...
I would actually do this: parse the line into fields while you're reading it.
while IFS=: read -ra fields; do
[[ ${fields[-1]} == "/bin/bash" ]] && printf "%s\n\n" "${fields[0]}"
done < /etc/passwd
This line is wrong:
if [ $("$line" | cut -d : -f 7)=="/bin/bash" ]
Also, this is not going to do what you want:
echo "\n"
Bash echo doesn't understand backslash-escaped characters without
-e. If you want to print a new line use just echo but notice that
the previous echo:
echo $line | cut -d : -f 1
will add a newline already.
You should always check your scripts with
shellcheck. The correct script would be:
#!/usr/bin/env bash
while read -r line
do
if [ "$(echo "$line" | cut -d : -f 7)" == "/bin/bash" ]
then
echo "$line" | cut -d : -f 1
fi
done < /etc/passwd
But notice that you don't really need a loop which is very slow and
could use the following awk one-liner:
awk -v FS=: '$7 == "/bin/bash" {print $1}' /etc/passwd
Instead of looping through the rows, and then checking for the /bin/bash part, why not use something like grep to get all the desired rows, like so:
grep ':/bin/bash$' /etc/passwd
Optionality, you can loop over the rows by using a simple while;
grep ':/bin/bash$' /etc/passwd | while read -r line ; do
echo "Processing $line"
done
Don't do while read | cut. Use IFS as:
#!/bin/sh
while IFS=: read name passwd uid gid gecos home shell; do
if test "$shell" = /bin/bash; then
echo "$name"
fi
done < /etc/passwd
But for this particular use case, it's probably better to do:
awk '$7 == "/bin/bash"{print $1}' FS=: /etc/passwd
The issue your code has is a common error. Consider the line:
if [ $("$line" | cut -d : -f 7)=="/bin/bash" ]
Assume you have a value in $line in which the final field is /bin/dash. The process substitution will insert the string /bin/dash, and bash will attempt to execute:
if [ /bin/dash==/bin/bash ]
since /bin/bash==/bin/bash is a non-empty string, the command [ /bin/bash==/bin/bash ] returns succesfully. It does not perform any sort of string comparison. In order for [ to do a string comparison, you need to pass it 4 arguments. For example, [ /bin/dash = /bin/bash ] would fail. Note the 4 arguments to that call are /bin/dash, =, /bin/bash, and ]. [ is an incredibly bizarre command that requires its final argument to be ]. I strongly recommend never using it, and replacing it instead with its cousin test (very closely related, indeed both test and [ used to be linked to the same executable) which behaves exactly the same but does not require its final argument to be ].
I have been busting my head all day long without coming up with a sucessfull solution.
Setup:
We have Linux RHEL 8.3 and a file, script.sh
There is an enviroment variable set by an application with a dynamic string in it.
export PROGARM_VAR="abc10,def20,ghi30"
The delimiter is always "," and the values inside vary from 1 to 20.
Inside the script I have defined 20 variables which take the values
using "cut" command I take each value and assign it to a variable
var1=$(echo $PROGARM_VAR | cut -f1 -d,)
var2=$(echo $PROGARM_VAR | cut -f2 -d,)
var3=$(echo $PROGARM_VAR | cut -f3 -d,)
var4=$(echo $PROGARM_VAR | cut -f4 -d,)
etc
In our case we will have:
var1="abc10" var2="def20" var3="ghi30" and var4="" which is empty
The loop must take each variable, test if its not empty and execute 10 pages of code using the tested variable. When it reaches an empty variable it should break.
Could you give me a hand please?
Thank you
Just split it with a comma. There are endless possibilities. You could:
10_pages_of_code() { echo "$1"; }
IFS=, read -a -r vars <<<"abc10,def20,ghi30"
for i in "${vars[#]}"; do 10_pages_of_code "$i"; done
or:
printf "%s" "abc10,def20,ghi30" | xargs -n1 -d, bash -c 'echo 10_pages_of_code "$1"' _
A safer code could use readarray instead of read to properly handle newlines in values, but I doubt that matters for you:
IFS= readarray -d , -t vars < <(printf "%s" "abc10,def20,ghi30")
You could also read in a stream up:
while IFS= read -r -d, var || [[ -n "$var" ]]; do
10_pages_of_code "$var"
done < <(printf "%s" "abc10,def20,ghi30")
But still you could do it with cut... just actually write a loop and use an iterator.
i=0
while var=$(printf "%s\n" "$PROGARM_VAR" | cut -f"$i" -d,) && [[ -n "$var" ]]; do
10_pages_of_code "$var"
((i++))
done
or
echo "$PROGRAM_VAR" | tr , \\n | while read var; do
: something with $var
done
I have multiple fasta files, where the first line always contains a > with multiple words, for example:
File_1.fasta:
>KY620313.1 Hepatitis C virus isolate sP171215 polyprotein gene, complete cds
File_2.fasta:
>KY620314.1 Hepatitis C virus isolate sP131957 polyprotein gene, complete cds
File_3.fasta:
>KY620315.1 Hepatitis C virus isolate sP127952 polyprotein gene, complete cds
I would like to take the word starting with sP* from each file and rename each file to this string (for example: File_1.fasta to sP171215.fasta).
So far I have this:
$ for match in "$(grep -ro '>')";do
fname=$("echo $match|awk '{print $6}'")
echo mv "$match" "$fname"
done
But it doesn't work, I always get the error:
grep: warning: recursive search of stdin
I hope you can help me!
you can use something like this:
grep '>' *.fasta | while read -r line ; do
new_name="$(echo $line | cut -d' ' -f 6)"
old_name="$(echo $line | cut -d':' -f 1)"
mv $old_name "$new_name.fasta"
done
It searches for *.fasta files and handles every "hitted" line
it splits each result of grep by spaces and gets the 6th element as new name
it splits each result of grep by : and gets the first element as old name
it
moves/renames from old filename to new filename
There are several things going on with this code.
For a start, .. I actually don't get this particular error, and this might be due to different versions.
It might resolve to the fact that grep interprets '>' the same as > due to bash expansion being done badly. I would suggest maybe going for "\>".
Secondly:
fname=$("echo $match|awk '{print $6}'")
The quotes inside serve unintended purpose. Your code should like like this, if anything:
fname="$(echo $match|awk '{print $6}')"
Lastly, to properly retrieve your data, this should be your final code:
for match in "$(grep -Hr "\>")"; do
fname="$(echo "$match" | cut -d: -f1)"
new_fname="$(echo "$match" | grep -o "sP[^ ]*")".fasta
echo mv "$fname" "$new_fname"
done
Explanations:
grep -H -> you want your grep to explicitly use "Include Filename", just in case other shell environments decide to alias grep to grep -h (no filenames)
you don't want to be doing grep -o on your file search, as you want to have both the filename and the "new filename" in one data entry.
Although, i don't see why you would search for '>' and not directory for 'sP' as such:
for match in "$(grep -Hro "sP[0-9]*")"
This is not the exact same behaviour, and has different edge cases, but it just might work for you.
Quite straightforward in (g)awk :
create a file "script.awk":
FNR == 1 {
for (i=1; i<=NF; i++) {
if (index($i, "sP")==1) {
print "mv", FILENAME, $i ".fasta"
nextfile
}
}
}
use it :
awk -f script.awk *.fasta > cmmd.txt
check the content of the output.
mv File_1.fasta sP171215.fasta
mv File_2.fasta sP131957.fasta
if ok, launch rename with . cmmd.txt
For all fasta files in directory, search their first line for the first word starting with sP and rename them using that word as the basename.
Using a bash array:
for f in *.fasta; do
arr=( $(head -1 "$f") )
for word in "${arr[#]}"; do
[[ "$word" =~ ^sP* ]] && echo mv "$f" "${word}.fasta" && break
done
done
or using grep:
for f in *.fasta; do
word=$(head -1 "$f" | grep -o "\bsP\w*")
[ -z "$word" ] || echo mv "$f" "${word}.fasta"
done
Note: remove echo after you are ok with testing.
How to fetch value of variable of variable (parameter expansion) in unix?
I have a text file(comma separated) filename='workdir.txt' as below which I am reading in unix shell script:
$AC_WORKDIR,current,FILE
$AC_WORKDIR,1 week,DIR
and so on
$AC_WORKDIR is env varriable AC_WORKDIR="/home/ascxd01/data/workdir" already defined.
My code is as below:
filename='workdir.txt'
while read line; do
work_dir=`echo $line | cut -d',' -f1`
echo "$work_dir"
done< $filename
When I am doing echo "$work_dir" its giving me $AC_WORKDIR however I want the actual value of $AC_WORKDIR which is "/home/ascxd01/data/workdir"
Please tell me how to do it.
If you drop the leading $ the indirect expansion should work normally, eg.
while read line; do
work_dir=`echo $line | cut -d',' -f1 | tr -d '$'` # delete "$"
echo "${!work_dir}"
done< $filename
The linked answer provides other maybe less good alternatives that would work, like eval "echo $work_dir".
Id like to convert it to uppercase for the simple purpose of formatting so it will adhere to a future case statement. As I thought case statements are case sensitive.
I see all over the place the tr command used in concert with echo commands to give you immediate results such as:
echo "Enter in Location (i.e. SDD-134)"
read answer (user enters "cfg"
echo $answer | tr '[:lower:]' '[:upper:]' which produced
cfg # first echo not upper?
echo $answer #echo it again and it is now upper...
CFG
This version doesn't require bash, but uses a pipe:
read -p "Enter in Location (i.e. SDD-134) " answer
answer=$(echo "$answer" | tr '[:lower:]' '[:upper:]')
echo "$answer"
And if you're using bash and don't care about portability you can replace the second line with this:
answer="${answer^^}"
Check the "Parameter Expansion" section of bash's man page for details.
Echoing a variable through tr will output the value, it won't change the value of the variable:
answer='cfg'
echo $answer | tr '[:lower:]' '[:upper:]'
# outputs uppercase but $answer is still lowercase
You need to reassign the variable if you want to refer to it later:
answer='cfg'
answer=$(echo $answer | tr '[:lower:]' '[:upper:]')
echo $answer
# $answer is now uppercase
In bash version 4 or greater:
answer=${answer^^*}
It is not clear what you are asking, but if you are trying to convert the user input to uppercase, just do:
sed 1q | tr '[:lower:]' '[:upper:]' | read answer
In shells that do not run the read in a subshell (eg zsh), this will work directly. To do this in bash, you need to do something like:
printf "Enter in Location (i.e. SDD-134): "
sed 1q | tr '[:lower:]' '[:upper:]' | { read answer; echo $answer; }
After the subshell closes, answer is an unset variable.
good and clear way to uppercase variable is
$var=`echo $var|tr '[:lower:]' '[:upper:]'`
Note Bene a back quotes