Why is my for loop not iterating in Linux Shell? - linux

I am trying to run an alphabetical sequence in Linux shell. I believe my syntax is correct, but its not giving me each letter, but rather {a..z} all at once. Here is what I have:
COUNTER=0;
for X in {a..z};
do
# Make sure usb is mounted before trying anything
sudo mkdir /media/mounts/sd${X}1
sudo mount /dev/sd${X}1 /media/mounts/sd${X}1
# Find player number from Linux mount point
DATA_DIR=/media/mounts/sd${X}1;
PLAYER_FILE_PATH=`find $DATA_DIR -name "player.*"`;
PLAYER_FILE_ONLY=`basename -- $PLAYER_FILE_PATH`;
PLAYER_NUMBER=`echo "${PLAYER_FILE_ONLY##*.}"`;
# Make sure usb device exists and player number is not empty
if [ -n "$PLAYER_NUMBER" ] && [ $PLAYER_NUMBER -gt 0 ]
then
# Let user know which tracer was found
echo "$PLAYER_NUMBER was found, creating directory to store its .dat files" | tee -a $ERR_LOG_PATH;
echo "${PLAYER_NUMBER}: " >> $LOG_PATH;
let COUNTER++;
# Create directory for player number to dump .dat files and log any errors at output
sudo mkdir $DUMP_DIR/$PLAYER_NUMBER;
NUMBER_DATS=`ls $DATA_DIR/MountsData/*.dat | wc -l`;
DAT_COUNT_ARRAY[$PLAYER_NUMBER]=$NUMBER_DATS
echo "USB $PLAYER_NUMBER has $NUMBER_DATS .dat files";
echo "${NUMBER_DATS}\r\n" >> $LOG_PATH;
cp $DATA_DIR/MountsData/*.dat $DUMP_DIR/$PLAYER_NUMBER 2>> $ERR_LOG_PATH;
# Remove all .dat files directly from usb and log any errors at output
sudo rm $DATA_DIR/MountsData/*.dat 2>> $ERR_LOG_PATH;
fi
done
I get the resulting output:
mount: special device /dev/sd{a..z}1 does not exist
I have tried changing the variable names, capitalization, and referring to them as $X instead of ${X}. I am currently using a Raspberry Pi running Raspbian with a standard Raspbian shell. I ran some test to ensure syntax was right in the terminal and seems like the for loop structure is correct. Am I missing something?

you have to execute it with bash.
some os link sh to bash, but some of them link to other shell apps like the dash in ubuntu.
to check the linked app in your sh, try to execute this command:
file -h /bin/sh

You can write a portable function that will generate the list in a POSIX compatible way. For example to generate a range of ASCII characters from any character (with lower ASCII value) to any ASCII character, you can do:
genasciirange() {
[ -n "$1" -a -n "$2" ] || return 1
local a=$(LC_CTYPE=C printf '%d' "'$1")
local b=$(LC_CTYPE=C printf '%d' "'$2")
[ "$a" -lt "$b" -a "$a" -lt 256 -a "$b" -lt 256 ] || return 1
local c="$a"
while [ "$c" -le "$b" ]; do
printf " \\$(printf '%03o' "$c")"
c=$((c+1))
done
printf "\n"
}
(where -a is the older AND for conditions within [...], you can replace with [...] && [...] if you desire)
The conversion from character to ASCII value is handled by LC_CTYPE=C printf '%d' "'$somechar" and from ASCII value to character by converting to octal value and then outputting the escaped octal code, e.g. printf " \\$(printf '%03o' "$someasciival")"
Example Use/Output
genasciirange a z
a b c d e f g h i j k l m n o p q r s t u v w x y z
or for example:
genasciirange Q q
Q R S T U V W X Y Z [ \ ] ^ _ ` a b c d e f g h i j k l m n o p q
In your case, you can include the function and replace the brace-expansion with:
for X in $(genasciirange a z)
Using the jot Utility
A fairly common utility available is jot. It is simply an advanced seq that can generate sequential or random data. For example, for a-z you could do:
jot -s " " -w %c 26 a
a b c d e f g h i j k l m n o p q r s t u v w x y z
It is likely available for your Linux distribution already. If not, you can build from source athena-jot-9.0.orig.tar.gz from Ubuntu Source Package: athena-jot (9.0-7)
awk can be used as well.
Look things over and let me know if you have questions.

Related

Basic Quadratic formula calculator shell script trouble

This is my very first shell script for a Unix class, this is one of the scripts I hope to submit for my final. However there are a few kinks I cannot seem to clear up, it seems to be arithmetic operation errors, and I can't seem to figure it out. Please be kind! thank you so much for your time.
lightgreen=`echo -en "\e[92m"
echo What are the values of a, b \& c?
LIGHTRED=`echo -en "\e[101m"
echo a value:
read a
echo b value:
read b
echo c value:
read c
discrim=$(($b**2 - 4*$a*$c))
sqrtd=$((sqrt($discrim) | bc ))
echo test $sqrtd
echo ${lightgreen}The discriminant is:${discrim}
#xone=$((( -$b + sqrt$discrim) / (2 * $a) | bc ))
#xtwo=$((( -$b - sqrt$discrim) / (2 * $a) | bc ))
xone=$((echo (-1*$b + sqrt($discrim)) / (2*$a) | bc ))
xtwo=$((echo (-1*$b - sqrt($discrim)) / (2*$a) | bc ))
echo ${lightgreen}The discriminant is:${discrim}
#if [$discrim -lt 0 ]
# echo $LIGHTRED There are no real solutions.
#
#
#
echo The two solutions are $xone $xtwo
I have tried to mess with the syntax a good amount, I'm not sure if it's the parentheses that mess me up or the sqrt function, I have tried to incorporate | bc but to no avail. Any help is greatly appreciated! :)
Don't hesitate to call man bash, man bc manual pages.
Use https://www.shellcheck.net/ to check your shell scripts.
Shellcheck also exists on command line and in Visual Studio Code with extension.
#! /usr/bin/env bash
# The first line is very important to now the name of the interpreter
# Always close " , ' , or ` sequences with same character
# Do not use old `...` syntax, replaced by $(...)
# Here, use $'...', to assign value with \... sequences
lightgreen=$'\e[92m'
lightred=$'\e[101m'
normal=$'\e[0m'
# It's better to put phrase between "..." or '...'
echo "What are the values of a, b & c?"
# Use read -p option to specify prompt
# Use read -r option to not act backslash as an escape character
read -p "a value: " -r a
read -p "b value: " -r b
read -p "c value: " -r c
# With bash only, it's only possible to use integer values
# discrim=$(($b**2 - 4*$a*$c))
# use bc instead
discrim=$(bc -l <<<"$b^2 - 4*$a*$c")
# The syntax:
# bc <<<"..."
# is equivalent to:
# echo "..." | bc
# but without pipe (|)
# Close the color change with normal return
echo "${lightgreen}The discriminant is: ${discrim}${normal}"
if [[ "${discrim:0:1}" == "-" ]]; then
echo "${lightred}There are no real solutions${normal}"
# ... complex ...
else
sqrtd=$(bc -l <<<"sqrt($discrim)")
echo "sqrt($discrim)=$sqrtd"
xone=$(bc -l <<<"(-1*$b + $sqrtd) / (2*$a)")
xtwo=$(bc -l <<<"(-1*$b - $sqrtd) / (2*$a)")
echo "The two solutions are: $xone and $xtwo"
fi

How to convert result as Integer in bash

when I do
$ ls | wc -l
703
It gave me the result 703, I want to print 702 (703-1)
How can I do it in bash?
You can use arithmetic expansion:
result=$(( $(ls | wc - l) - 1))
or just ignore one of the files
result=$(ls | tail -n+2 | wc -l)
Note that it doesn't work if filenames contain the newline character; use ls -q to get one filename per line in such a case. This applies to the first solution, too, if you're interested in the number of files and not the number of lines in their names.
(Cheeky answer) Remove one line from the output before counting :D
ls | sed '1d' | wc -l
How to convert result as Integer in bash
#choroba has already answered this question and it should have solved OP's problem. However, I want to add more to his answer.
The OP's wants to convert the result into Integer but Bash doesn't have any data type like Integer.
Unlike many other programming languages, Bash does not segregate its variables by "type." Essentially, Bash variables are character strings, but, depending on context, Bash permits arithmetic operations and comparisons on variables. The determining factor is whether the value of a variable contains only digits.
See this for arithmetic operation in Bash.
See this for a best example to learn the untyped nature of Bash. I have posted the example below:
#!/bin/bash
# int-or-string.sh
a=2334 # Integer.
let "a += 1"
echo "a = $a " # a = 2335
echo # Integer, still.
b=${a/23/BB} # Substitute "BB" for "23".
# This transforms $b into a string.
echo "b = $b" # b = BB35
declare -i b # Declaring it an integer doesn't help.
echo "b = $b" # b = BB35
let "b += 1" # BB35 + 1
echo "b = $b" # b = 1
echo # Bash sets the "integer value" of a string to 0.
c=BB34
echo "c = $c" # c = BB34
d=${c/BB/23} # Substitute "23" for "BB".
# This makes $d an integer.
echo "d = $d" # d = 2334
let "d += 1" # 2334 + 1
echo "d = $d" # d = 2335
echo
# What about null variables?
e='' # ... Or e="" ... Or e=
echo "e = $e" # e =
let "e += 1" # Arithmetic operations allowed on a null variable?
echo "e = $e" # e = 1
echo # Null variable transformed into an integer.
# What about undeclared variables?
echo "f = $f" # f =
let "f += 1" # Arithmetic operations allowed?
echo "f = $f" # f = 1
echo # Undeclared variable transformed into an integer.
#
# However ...
let "f /= $undecl_var" # Divide by zero?
# let: f /= : syntax error: operand expected (error token is " ")
# Syntax error! Variable $undecl_var is not set to zero here!
#
# But still ...
let "f /= 0"
# let: f /= 0: division by 0 (error token is "0")
# Expected behavior.
# Bash (usually) sets the "integer value" of null to zero
#+ when performing an arithmetic operation.
# But, don't try this at home, folks!
# It's undocumented and probably non-portable behavior.
# Conclusion: Variables in Bash are untyped,
#+ with all attendant consequences.
exit $?

How to print something to the right-most of the console in Linux shell script

Say I want to search for "ERROR" within a bunch of log files.
I want to print one line for every file that contains "ERROR".
In each line, I want to print the log file path on the left-most edge while the number of "ERROR" on the right-most edge.
I tried using:
printf "%-50s %d" $filePath $errorNumber
...but it's not perfect, since the black console can vary greatly, and the file path sometimes can be quite long.
Just for the pleasure of the eyes, but I am simply incapable of doing so.
Can anyone help me to solve this problem?
Using bash and printf:
printf "%-$(( COLUMNS - ${#errorNumber} ))s%s" \
"$filePath" "$errorNumber"
How it works:
$COLUMNS is the shell's terminal width.
printf does left alignment by putting a - after the %. So printf "%-25s%s\n" foo bar prints "foo", then 22 spaces, then "bar".
bash uses the # as a parameter length variable prefix, so if x=foo, then ${#x} is 3.
Fancy version, suppose the two variables are longer than will fit in one column; if so print them on as many lines as are needed:
printf "%-$(( COLUMNS * ( 1 + ( ${#filePath} + ${#errorNumber} ) / COLUMNS ) \
- ${#errorNumber} ))s%s" "$filePath" "$errorNumber"
Generalized to a function. Syntax is printfLR foo bar, or printfLR < file:
printfLR() { if [ "$1" ] ; then echo "$#" ; else cat ; fi |
while read l r ; do
printf "%-$(( ( 1 + ( ${#l} + ${#r} ) / COLUMNS ) \
* COLUMNS - ${#r} ))s%s" "$l" "$r"
done ; }
Test with:
# command line args
printfLR foo bar
# stdin
fortune | tr -s ' \t' '\n\n' | paste - - | printfLR

Restarting a job for supercomputer while looping a command for millions of files?

I'm using a supercomputer which is using the famous #PBS. My walltime is 48 hours and this is not enough to process million files.
My file names are like :
AAAAAA.pdb
DAAAAA.pdb
EAAAAA.pdb
FAAAAA.pdb
...
All possibles letters are "A D E F G H I K L M N P Q R S T V W Y".
I want to use a script like this :
for file in /dir/*
do
cmd [option] $file >> results.out
done
But I must use a restart for the wall time. With numbers I would have put a counter but with specific letters I don't know how to write the last file to start from this checkpoink. Like :
if [ -f next.seq ]; then
seq=`cat next.seq`
else
...
for file in /dir/seq
do
cmd [option] $file >> results.out
done
...
let seq=seq+1
echo $seq > next.seq

Printing IFS values from file

Script:
#!/bin/ksh
FILENAME=$1
while read RECORD VALUE
do
echo ${RECORD} ${VALUE} "X"
done <"$FILENAME"
input file:
A 1
B 2
The output of script:
X1
X2
If I remove from echo "x", e.g.
echo ${RECORD} ${VALUE}
I am getting
A 1
B 2
what is wrong?
Update:
If I do
echo "X" ${RECORD} ${VALUE}
it prints correctly:
X A 1
X B 2
and :
echo ${RECORD} "X"
also prints correctly, so i am guessing the issues is with VALUE that maybe contains return carriage symbol (as input file was created on windows)
adding this inside the loop:
VALUE=`echo $VALUE| tr -d '\r'`
solved the issue, if you have a better solution you are more than welcome.
There is a parameter expansion operator you can use to remove a character from the end of a value, if it is present.
VALUE=${VALUE%$'\r'}
This is handled in-shell, without needing to start a new process.

Resources