I am trying to assign a unique number to my strings that are redirected and stored in a file.
You have to fill in a form, and I want to send a unique number with it.
example:
echo fill in the form
echo place
read place
date
"place: $place, time $(date) >> List
It has to look something like this.
outcome in List
number 1, place, time
number 2, place, time
number 3, place, time
I used a loop but I got the following outcome.
number 0, place, time
number 0, place, time.
I think I need a function that checks the last number given in the file and add 1 to it, but I wonder if there is an easier way.
Perhaps this one:
#!/bin/bash
# Optionally truncate file
# : > List
I=0
while
read -p "Place: " PLACE
read -p "Time: " TIME
echo "number $((++I)), $PLACE, $TIME" >> List
read -n 1 -p "Continue? " && [[ $REPLY == [yY] ]]
do
continue
done
Update:
#!/bin/bash
# Optionally truncate file
# : > List
shopt -s extglob
for (( I = 1;; ++I )); do
for (( ;; )); do
read -p "Place: " PLACE
read -p "Time: " TIME
until
read -p "Save data? "
[[ $REPLY == [nN]?([oO]) ]]
do
[[ $REPLY == [yY]?([eE][sS]) ]] && break 2
echo "Please answer Y[es] or N[o]."
done
done
echo "Saving \"number $I, $PLACE, $TIME\"."
echo "number $I, $PLACE, $TIME" >> List
until
read -p "Continue? "
[[ $REPLY == [yY]?([eE][sS]) ]]
do
[[ $REPLY == [nN]?([oO]) ]] && break 2
echo "Please answer Y[es] or N[o]."
done
echo
done
To generate a 16-char random hex string, you can use r=$(openssl rand -hex 8)
To find the last number used and increment it, you can do
prev=$(awk -F, 'END {print $1}' List)
printf "%d, place:%s, time:%s\n" $((prev+1)) "$place" "$(date)" >> List
This is subject to a race condition if the script can be executed simultaneously
To start at 1:
if [[ ! -f List ]]; then
prev=1
else
prev=$(awk -F, 'END {print $1}' List)
fi
printf "%d, place:%s, time:%s\n" $((prev+1)) "$place" "$(date)" >> List
Related
My background is in SQL but I've been learning Bash to create tools to help non-Linux users find what they need from my Linux system - I am pretty green with Bash, I apologize if this looks a bit dumb.
The goal of the script is to essentially display all directories within the current directory to the user, and allow them to input 1-9 to navigate to lower directories.
My sticking point is that I'm trying to use arrays to define potential filepaths, since in practice new directories will be added over time and it is not practical to edit the script each time a filepath is added.
Here's my prototype so far, currently it navigates into Test1, Test2, or Test3 then echos pwd to prove it is there.
#Global Variables
DIR_MAIN='/home/admin/Testhome'
#Potential Filepaths
#/home/admin/Testhome/Test1/Test1-1/
#/home/admin/Testhome/Test1/Test1-2/
#/home/admin/Testhome/Test2/Test2-1/
#/home/admin/Testhome/Test2/Test2-2/
#/home/admin/Testhome/Test3/Test3-1/
#/home/admin/Testhome/Test3/Test3-2/
#Defining Array for first user input
arr=($(ls $DIR_MAIN))
#System to count total number of directories in filepath, then present to user for numbered selection
cnt=0
for i in ${arr[#]}
do
cnt=$(($cnt+1))
echo "$cnt) $i"
done
read -p "Select a folder from the list: " answer
case $answer in
1)
cd $DIR_MAIN/${arr[0]}
echo "Welcome to $(pwd)"
;;
2)
cd $DIR_MAIN/${arr[1]}
echo "Welcome to $(pwd)"
;;
3)
cd $DIR_MAIN/${arr[2]}
echo "Welcome to $(pwd)"
;;
esac
I've tried the following, but it doesn't like the syntax (to someone experienced I'm sure these case statements look like a grenade went off in vim).
I'm beginning to wonder if the SELECT CASE road I'm going down is appropriate, or if there is an entirely better way.
#User Input Start
echo "What is the secret number?"
while :
do
read STRING1
case $STRING1 in
1)
echo "Enter the number matching the directory you want and I will go there"
echo "1 - ${arr[0]}"
echo "2 - ${arr[1]}"
echo "3 - ${arr[2]}"
read STRING2
case $STRING2 in
1)
cd $DIR_MAIN/${arr[0]}
echo "Welcome to" $(pwd)
2)
cd $DIR_MAIN/${arr[1]}
echo "Welcome to" $(pwd)
3)
cd $DIR_MAIN/${arr[2]}
echo "Welcome to" $(pwd)
*)
echo "Thats not an option and you know it"
*)
echo "1 is the secret number, enter 1 or nothing will happen"
;;
esac
#break needs to be down here somewhere
done
Ultimately I know I'll need to variabilize a local array once I'm in Test2 for example (since in practice, this could descend as far as /Test2/Test2-9 and there would be tons of redundant code to account for this manually).
For now, I'm just looking for the best way to present the /Test2-1 and /Test2-2 filepaths to the user and allow them to make that selection after navigating to /Test2/
This might do what you wanted.
#!/usr/bin/env bash
shopt -s nullglob
n=1
for i in /home/admin/Testhome/Test[0-9]*/*; do
printf '%d) %s\n' "$n" "$i"
array[n]="$i"
((n++))
done
(( ${#array[*]} )) || {
printf 'It looks like there is/are no directory listed!\n' >&2
printf 'Please check if the directories in question exists!\n' >&2
return 1
}
dir_pattern_indices=$(IFS='|'; printf '%s' "#(${!array[*]})")
printf '\n'
read -rp "Select a folder from the list: " answer
if [[ -z $answer ]]; then
printf 'Please select a number and try again!' >&2
exit 1
elif [[ $answer != $dir_pattern_indices ]]; then
printf 'Invalid option %s\n' "$answer" >&2
exit 1
fi
for j in "${!array[#]}"; do
if [[ $answer == "$j" ]]; then
cd "${array[j]}" || exit
printf 'Welcome to %s\n' "$(pwd)"
break
fi
done
The script needs to be sourced e.g.
source ./myscript
because of the cd command. See Why can't I change directory using a script.
Using a function instead of a script.
Let's just name the function list_dir
list_dir() {
shopt -s nullglob
declare -a array
local answer dir_pattern_indices i j n
n=1
for i in /home/admin/Testhome/Test[0-9]*/*; do
printf '%d) %s\n' "$n" "$i"
array[n]="$i"
((n++))
done
(( ${#array[*]} )) || {
printf 'It looks like there is/are no directory listed!\n' >&2
printf 'Please check if the directories in question exists!\n' >&2
return 1
}
dir_pattern_indices=$(IFS='|'; printf '%s' "#(${!array[*]})")
printf '\n'
read -rp "Select a folder from the list: " answer
if [[ -z $answer ]]; then
printf 'Please select a number and try again!' >&2
return 1
elif [[ $answer != $dir_pattern_indices ]]; then
printf 'Invalid option %s\n' "$answer" >&2
return 1
fi
for j in "${!array[#]}"; do
if [[ $answer == "$j" ]]; then
cd "${array[j]}" || return
printf 'Welcome to %s\n' "$(pwd)"
break
fi
done
}
All of the array names and variables are declared local to the function in order not to pollute the interactive/enviromental shell variables.
Put that somewhere in your shellrc file, like say in ~/.bashrc then source it again after you have edited that shellrc file.
source ~/.bashrc
Then just call the function name.
list_dir
I took what #Jetchisel wrote and ran with it - I see they updated their code as well.
Between that code and what I hacked together piggybacking off what he wrote, I'm hoping future viewers will have what they need to solve this problem!
My code includes a generic logging function (can write to a log file if you define it and uncomment those logging lines, for a script this size I just use it to output debugging messages), everything below is the sequence used.
As he mentioned the "0" element needs to be removed from the array for this to behave as expected, as a quick hack I ended up assigning array element 0 as null and adding logic to ignore null.
This will also pull pretty much anything in the filepath, not just directories, so more tweaking may be required for future uses but this serves the role I need it for!
Thank you again #Jetchisel !
#hopt -s nullglob
DIR_MAIN='/home/admin/Testhome'
Dir_Cur="$DIR_MAIN"
LOG_LEVEL=1
array=(NULL $(ls $DIR_MAIN))
########FUNCTION LIST#########
####Generic Logging Function
Log_Message()
{
local logLevel=$1
local logMessage=$2
local logDebug=$3
local dt=$(date "+%Y-%m-%d %T")
##Check log level
if [ "$logLevel" == 5 ]
then local logLabel='INFO'
elif [ "$logLevel" == 1 ]
then local logLabel='DEBUG'
elif [ "$logLevel" == 2 ]
then local logLabel='INFO'
elif [ "$logLevel" == 3 ]
then local logLabel='WARN'
elif [ "$logLevel" == 4 ]
then local logLabel='ERROR'
fi
##Check conf log level
if [ "$LOG_LEVEL" == 1 ]
then #echo "$dt [$logLabel] $logMessage" >> $LOG_FILE ##Log Message
echo "$dt [$logLabel] $logMessage" ##Echo Message to Terminal
##Check if Debug Empty
if [ "$logDebug" != "" ]
then #echo "$dt [DEBUG] $logDebug" >> $LOG_FILE ##Extra Debug Info
echo "$dt [DEBUG] $logDebug" ##Extra Debug Info
fi
elif [ "$logLevel" -ge "$LOG_LEVEL" ]
then #echo "$dt [$logLabel] $logMessage" >> "$LOG_FILE" ##Log Message
echo "$dt [$logLabel] $logMessage"
fi
}
####
####Function_One
##Removes 0 position in array by marking it null, generates [1-X] list with further filepaths
Function_One()
{
Log_Message "1" "entered Function_One"
local local_array=("$#")
Log_Message "1" "${local_array[*]}"
n=1
for i in "${local_array[#]}"; do
if [ "$i" != "NULL" ]
then
printf '%d) %s\n' "$n" "$i"
array[n]="$i"
((n++))
fi
done
printf '\n'
read -rp "Select a folder from the list: " answer
for j in "${!local_array[#]}"; do
if [[ $answer == "$j" ]]; then
cd "$Dir_Cur/${local_array[j]}" || exit
printf 'Welcome to %s\n' "$(pwd)"
break
fi
done
}
####
########FUNCTION LIST END#########
########MAIN SEQUENCE########
echo "Script start"
Function_One "${array[#]}"
Dir_Cur="$(pwd)"
array2=(NULL $(ls $Dir_Cur))
Function_One "${array2[#]}"
Dir_Cur="$(pwd)"
$Dir_Cur/test_success.sh
echo "Script end"
########
I have a flat file called items that I want to populate a select but I want to be able to choose multiple items at one time.
contents of items file:
cat 1
dog 1
pig 1
cherry 2
apple 2
Basic script:
#!/bin/bash
PS3=$'\n\nSelect the animals you like: '
options=$(grep '1' items|grep -v '^#' |awk '{ print $1 }')
select choice in $options
do
echo "you selected: $choice"
done
exit 0
The way it flows now is I can only select one option at at time. I'd like to be able to answer 1,3 or 1 3 and have it respond "you selected: cat pig"
Thank you,
Tazmarine
I can offer a somewhat different approach that uses a different selection prompt style. Here's a bash function that allows user to select multiple options with arrow keys and Space, and confirm with Enter. It has a nice menu-like feel. I wrote it with the help of https://unix.stackexchange.com/a/415155. It can be called like this:
multiselect result "Option 1;Option 2;Option 3" "true;;true"
The result is stored as an array in a variable with the name supplied as the first argument. Last argument is optional and is used for making some options selected by default. It looks like this:
function prompt_for_multiselect {
# little helpers for terminal print control and key input
ESC=$( printf "\033")
cursor_blink_on() { printf "$ESC[?25h"; }
cursor_blink_off() { printf "$ESC[?25l"; }
cursor_to() { printf "$ESC[$1;${2:-1}H"; }
print_inactive() { printf "$2 $1 "; }
print_active() { printf "$2 $ESC[7m $1 $ESC[27m"; }
get_cursor_row() { IFS=';' read -sdR -p $'\E[6n' ROW COL; echo ${ROW#*[}; }
key_input() {
local key
IFS= read -rsn1 key 2>/dev/null >&2
if [[ $key = "" ]]; then echo enter; fi;
if [[ $key = $'\x20' ]]; then echo space; fi;
if [[ $key = $'\x1b' ]]; then
read -rsn2 key
if [[ $key = [A ]]; then echo up; fi;
if [[ $key = [B ]]; then echo down; fi;
fi
}
toggle_option() {
local arr_name=$1
eval "local arr=(\"\${${arr_name}[#]}\")"
local option=$2
if [[ ${arr[option]} == true ]]; then
arr[option]=
else
arr[option]=true
fi
eval $arr_name='("${arr[#]}")'
}
local retval=$1
local options
local defaults
IFS=';' read -r -a options <<< "$2"
if [[ -z $3 ]]; then
defaults=()
else
IFS=';' read -r -a defaults <<< "$3"
fi
local selected=()
for ((i=0; i<${#options[#]}; i++)); do
selected+=("${defaults[i]}")
printf "\n"
done
# determine current screen position for overwriting the options
local lastrow=`get_cursor_row`
local startrow=$(($lastrow - ${#options[#]}))
# ensure cursor and input echoing back on upon a ctrl+c during read -s
trap "cursor_blink_on; stty echo; printf '\n'; exit" 2
cursor_blink_off
local active=0
while true; do
# print options by overwriting the last lines
local idx=0
for option in "${options[#]}"; do
local prefix="[ ]"
if [[ ${selected[idx]} == true ]]; then
prefix="[x]"
fi
cursor_to $(($startrow + $idx))
if [ $idx -eq $active ]; then
print_active "$option" "$prefix"
else
print_inactive "$option" "$prefix"
fi
((idx++))
done
# user key control
case `key_input` in
space) toggle_option selected $active;;
enter) break;;
up) ((active--));
if [ $active -lt 0 ]; then active=$((${#options[#]} - 1)); fi;;
down) ((active++));
if [ $active -ge ${#options[#]} ]; then active=0; fi;;
esac
done
# cursor position back to normal
cursor_to $lastrow
printf "\n"
cursor_blink_on
eval $retval='("${selected[#]}")'
}
You can not do that as such, but you can always record each individual selection:
#!/bin/bash
PS3=$'\n\nSelect the animals you like: '
options=$(grep '1' items|grep -v '^#' |awk '{ print $1 }')
# Array for storing the user's choices
choices=()
select choice in $options Finished
do
# Stop choosing on this option
[[ $choice = Finished ]] && break
# Append the choice to the array
choices+=( "$choice" )
echo "$choice, got it. Any others?"
done
# Write out each choice
printf "You selected the following: "
for choice in "${choices[#]}"
do
printf "%s " "$choice"
done
printf '\n'
exit 0
Here's an example interaction:
$ ./myscript
1) cat
2) dog
3) pig
4) Finished
Select the animals you like: 3
pig, got it. Any others?
Select the animals you like: 1
cat, got it. Any others?
Select the animals you like: 4
You selected the following: pig cat
If you instead want to be able to write 3 1 on the same line, you'll have to make your own menu loop with echo and read
This is what I came up with. This seems to works as I want. I want the final output to be comma separated:
#!/bin/bash
newarray=(all $(grep '1' items|grep -v '^#' |awk '{ print $1 }'))
options() {
num=0
for i in ${newarray[#]}; do
echo "$num) $i"
((num++))
done
}
getanimal() {
while [[ "$show_clean" =~ [A-Za-z] || -z "$show_clean" ]]; do
echo "What animal do you like: "
options
read -p 'Enter number: ' show
echo
show_clean=$(echo $show|sed 's/[,.:;]/ /g')
selected=$(\
for s in $show_clean; do
echo -n "\${newarray[${s}]},"
done)
selected_clean=$(echo $selected|sed 's/,$//')
done
eval echo "You selected $selected_clean"
}
getanimal
exit 0
My script is to ask for input 1 upper case at a time and end with 0 invalid input will need to be displayed and display the first valid upper letter.
#! /bin/sh
count=0
until [[ $n =~ 0 ]]; do
echo Inputs:
read n
if [[ $n =~ ^[A-Z]$ ]]; then
count=`expr $count + 1`
echo $n | sort > out.txt
fi
done
echo The total number of valid input letters:
echo $count
echo " "
echo The first valid input:
head -n 1 /filepath/out.txt
Output:
Inputs:
B
Inputs:
A
Inputs:
C
Inputs:
0
The total number of valid input letters:
3
The first valid input:
C
Question: It should result in A.
Any help will be appreciated.
This line:
echo $n | sort > out.txt
always zaps the file out.txt with just the latest input. Maybe you should use:
cp /dev/null out.txt # Before the loop
echo $n | sort -o out.txt out.txt -
The cp command creates an empty file. The sort command reads the existing file out.txt and its standard input (the new line), sorts the result and writes it out over out.txt.
This is OK for short inputs; it isn't very efficient if it needs to scale to thousands of lines.
Also, in Bash, you don't need to use expr for arithmetic:
((count++))
Use the following code.
#! /bin/sh
count=0
>out.txt
until [[ $n =~ 0 ]]; do
read -p 'Inputs: ' n
if [[ $n =~ ^[A-Z]$ ]]; then
count=`expr $count + 1`
echo $n >> out.txt
fi
done
echo The total number of valid input letters:
echo $count
echo " "
echo The first valid input:
sort out.txt |head -n 1
Output:
Inputs: B
Inputs: A
Inputs: C
Inputs: 0
The total number of valid input letters:
3
The first valid input:
A
Since you want only the smallest (in alphabet order) valid input, you don't need sort. Here's an alternative answer not using sort but just keep the smallest valid input:
#!/bin/sh
count=0
until [[ $n =~ 0 ]]; do
echo Inputs:
read n
if [[ $n =~ ^[A-Z]$ ]]; then
((count++))
if [ -z $first ] || [ `expr $n \< $first` -eq 1 ]; then
first=$n
fi
fi
done
echo The total number of valid input letters:
echo $count
echo " "
echo The first valid input:
echo $first
I am adding two numbers using the below script, but I don't want non-numeric character entry from user so I need to test and throw an error. How do I test the user entry?
echo add 2 numbers
read sum1
read sum2
let sum3=$sum1+$sum2
echo ans is $sum3
Use printf for testing if variable is valid integer:
isInt() {
[[ -n "$1" ]] && printf '%f' "$1" >/dev/null 2>&1 &&
echo "valid integer" || echo "invalid integer";
}
Aside from running every code path that has an NSLocalizedString in it, is there a way to verify that all NSLocalizedStrings have a key that actually exists in all your Localizable.strings files of all your bundles?
E.g. there wasn't a typo in one key such that NSLocalizedString won't find the key it's looking for?
OK I wrote a bash script to accomplish the above. Here it is. It took me hours so don't forget to up-vote me if you like. Feel free to make improvements, etc. I added a few comments suggesting potential improvements.
#!/bin/sh
# VerNSLocalizedStrings
while getopts "vsl:" arg; do
case $arg in
v)
verbose="yes"
;;
s)
stopOnMissing="yes"
;;
l)
lang=$OPTARG
;;
esac
done
if [[ -z $lang ]]
then
lang="en"
fi
searchDir=$lang.lproj
fileFound=`ls . | grep $searchDir`
if [[ -z $fileFound ]]
then
echo "dir "$searchDir" not found."
exit
fi
fileFound=`ls $searchDir/ | grep strings`
if [[ -z $fileFound ]]
then
echo "No .strings files found in dir "$searchDir"."
exit
fi
echo "Verifying NSLocalizationStrings in "$searchDir
# Get all the NSLocalizedString Commands
output=$(grep -R NSLocalizedString . --include="*.m")
# Go thru the NSLocalizedString commands line for line
count=$(( 0 ))
missing=$(( 0 ))
unusable=$(( 0 ))
OIFS="${IFS}"
NIFS=$'\n'
IFS="${NIFS}"
for LINE in ${output} ; do
IFS="${OIFS}"
# Now extract the key from it
# admittedly this only works if there are no line breaks between
# NSLocalizedStrings and the entire key,
# but it accounts for the keys it couldn't identify.
quotes=`echo $LINE | awk -F\" '{ for(i=2; i<=NF; i=i+2){ a = a"\""$i"\"""^";} {print a; a="";}}'`
key=`echo $quotes | cut -f1 -d"^"`
# If we couldn't find the key then flag problem
if [[ -z $key ]]
then
(( unusable += 1 ))
echo "Couldn't extract key: " $LINE
if [ -n "$stopOnMissing" ]
then
break
else
continue
fi
fi
# I don't know how grep works regarding length of string, only that
# if the string is too long then it doesn't find it in the file
keyLength=$(echo ${#key})
if [ $keyLength -gt 79 ]
then
(( unusable += 1 ))
echo "Key too long ("$keyLength"): " $key
if [ -n "$stopOnMissing" ]
then
break
else
continue
fi
fi
# It would be nice if this were a regular expression that allowed as many
# spaces as you want, even a line break then forced the quotes on the
# other side of the equal sign.
keyString=$key" ="
# Search for the key
found=$(iconv -sc -f utf-16 -t utf8 $searchDir/*.strings | grep "$keyString")
# damned if I know why some strings files are utf-16 and others are utf8
if [[ -z $found ]]
then
found=$(grep -r "$keyString" $searchDir/ --include=*.strings)
fi
# analyze the result
if [[ -z $found ]]
then
(( missing += 1 ))
echo "Missing: " $key "\n from: " $LINE
if [ -n "$stopOnMissing" ]
then
break
fi
else
if [ -n "$verbose" ]
then
echo "found: " $key
fi
fi
(( count += 1 ))
IFS="${NIFS}"
done
IFS="${OIFS}"
# It would also be nice if it went the other way and identified
# extraneous unused items in the strings files. But
# I've spent enough time on this for now
echo $count " keys analyzed"
echo $unusable " keys could not be determined"
echo $missing " keys missing"
To verify that all NSLocalizedStrings have a key that actually exists in all your Localizable.strings files or you missed localised you can enable the Localization enable "Show non-localized strings" option in the your project scheme editor.
Now run application you will see console logs for the missing localised string.