Создание скрипта с опциями для доступа к разным каталогам и файлам

12

Я долго пытался написать скрипт, который бы имел 2 аргумента, 1 с просьбой выбрать год и 2 с просьбой выбрать, хочу ли я, чтобы минимальная, максимальная, средняя или все были показаны как последняя строка из файлов, связанных в выбранный год.

По сути, у меня есть каталог, который содержит подкаталоги разных лет (2000, 2001, 2002 и т. Д.), В этих каталогах есть подкаталоги по месяцам и дням, которые содержат (а) файл (ы), информирующий о популяциях (но не реальной информации) разных города как последняя строчка. Это часть дерева каталога:

.
|-- 2000
|   |-- 01
|   |   `-- 18
|   |       `-- ff_1177818640
|   |-- 02
|   |   |-- 02
|   |   |   `-- ff_1669027271
|   |   |-- 03
|   |   |   `-- ff_234075290
|   |   |-- 10
|   |   |   `-- ff_1584524530
|   |   |-- 14
|   |   |   `-- ff_113807345
|   |   `-- 17
|   |       `-- ff_1452228827
|   |-- 03
|   |   |-- 06
|   |   |   `-- ff_58914249
|   |   `-- 11
|   |       `-- ff_2828212321
|   |-- 04
|   |   `-- 17
|   |       `-- ff_302131884
|   |-- 06
|   |   `-- 13
|   |       `-- ff_2175615745
|   |-- 07
|   |   |-- 07
|   |   |   `-- ff_918426998
|   |   `-- 24
|   |       `-- ff_2808316425
|   |-- 08
|   |   `-- 27
|   |       `-- ff_1449825497
|   |-- 09
|   |   `-- 19
|   |       `-- ff_110255856
|   `-- 12
|       `-- 08
|           `-- ff_1621190
|-- 2001
|   |-- 03
|   |   `-- 21
|   |       `-- ff_517010375
|   |-- 05
|   |   `-- 27
|   |       `-- ff_1458621098
|   |-- 06
|   |   |-- 07
|   |   |   `-- ff_155853916
|   |   |-- 25
|   |   |   |-- ff_2382312387
|   |   |   `-- ff_270731174
|   |   `-- 29
|   |       `-- ff_3228522859
|   |-- 07
|   |   `-- 28
|   |       `-- ff_3215021752
|   |-- 09
|   |   `-- 24
|   |       `-- ff_1080314364
|   `-- 11
|       `-- 24
|           `-- ff_2313722442

Все файлы отформатированы одинаково:

2019-04-03
Wednesday
Newcastle-upon-Tyne
255362

Мне нужно написать скрипт, чтобы спросить, какой год мне нужен (выбрать этот каталог), а затем спросить, хочу ли я показать среднее, минимальное, максимальное или все вышеперечисленное для населения (это последняя строка файлов).

Это то, что я до сих пор:

#!/bin/bash

function min () {
    echo $(sort -n populations | head -1)
}

function max () {
    echo $(sort -n populations | tail -1)
}

function avg () {
    count=0
    sum=0
    while read line ; do
        num='echo ${line#* }'
        sum='expr $sum + $num'
        count='expr $count + 1'
    done < populations
    avg='expr $sum / $count'
    echo $avg
}

echo "Please enter the year: "
read s1
echo "
        Enter an option:
        1. Minimum
        2. Maximum
        3. Average
        4. All"
read s2
#echo $s2
for file in $(find ~/filesToSort/$s1 -type f) ; do
    tail -1 $file >> populations
done
echo $(cat populations)
#min
#max
#avg
rm populations

Это позволяет мне выбирать каталоги, но не дает мне ответов, которые мне нужны, просто выплевывает последние строки моих файлов.

Манты
источник
так что вы хотите сделать математику из чисел, хранящихся в файле «население»?
cmak.fr

Ответы:

7

Если бы я реализовывал это в bash, я бы сделал следующее. Я не буду комментировать это много: не стесняйтесь задавать конкретные вопросы - сначала проверьте страницу руководства bash, если вы не знаете, как работает конкретная команда.

#!/bin/bash

# read the population from all the files
# map the filename to it's population figure
declare -A population
while IFS= read -d '' -r filename; do
    population["$filename"]=$(tail -1 "$filename")
done < <(find . -type f -print0)

# prompt the user for the year
read -rp "What year? " year

# find the relevant files for that year
year_files=()
for filename in "${!population[@]}"; do
    [[ $filename == ./"$year"/* ]] && year_files+=("$filename")
done
if [[ "${#year_files[@]}" -eq 0 ]]; then
    echo "No files for year '$year'"
    exit 1
fi

PS3="Select a function to calculate: "
select func in minimum maximum average quit; do
    case $func in
        minimum)
            min=${population[${year_files[0]}]}
            for file in "${year_files[@]}"; do
                if (( min > ${population[$file]} )); then
                    min=${population[$file]}
                fi
            done
            echo "Minimum for $year is $min"
            ;;
        maximum)
            max=${population[${year_files[0]}]}
            for file in "${year_files[@]}"; do
                if (( max < ${population[$file]} )); then
                    max=${population[$file]}
                fi
            done
            echo "Maximum for $year is $max"
            ;;
        average)
            count=0 sum=0
            for file in "${year_files[@]}"; do
                (( sum += ${population[$file]} ))
                (( count++ ))
            done
            echo "Average for $year is $(( sum / count ))"
            ;;
        quit) exit ;;
    esac
done
Гленн Джекман
источник
Должен быть другой вариант выбора как «Все»
αғsнιη
5

Я пишу простой awkскрипт, который делает то же, что и вы:

# read 'year' & 'option' from user
# or you can pass as argument to the command $1<-->$year & $2<-->$option

find /path/to/$year -type f -exec \
    awk -v select=$option '
        FNR==4 { sum+=$0; avg=sum/++count; 
                 max=(max>=$0?max:$0);
                 if (count==1) min=$0;
        }
        count>1 { min=(min<=$0?min:$0);
        }
    END{ stats=min","max","avg","min"\n"max"\n"avg;
         split(stats, to_print,",");
         print to_print[select];
    }' {} +

Объяснение в строке:

# read 'year' & 'option' from user
# or you can pass as argument to the command $1<-->$year & $2<-->$option

find /path/to/$year -type f -exec \
# find all files under "/path/to/$year". $year will be substitute with the value 
# of 'year' variable read from user-input or replace it with '$1' as first argument to the command

    awk -v select=$option '
    # read the value of shell 'option' variable into an awk 'select' variable 
    # replace with '$2' as argument to the command

        FNR==4 { sum+=$0; avg=sum/++count;
        # if it's 4th line of each input file, sum-up the value into 'sum' variable
        # and calculate the 'avg' too when 'count' will increment once each 4th record in a file is read

                 max=(max>=$0?max:$0);
                 # its a Ternary operator (condition?if-true:if-false) and finding maximum value

                 if (count==1) min=$0;
                 # keep the first file's 4th line's value as minimum. you could use `NR==4` instead
        }
        count>1 { min=(min<=$0?min:$0);
        # same as max, update the 'min' if value in current file is smaller than 'min' in previous file
        }
    END{ stats=min","max","avg","min"\n"max"\n"avg;
    # saving all variables' value into single variable with comma separated. I used <min"\n"max"\n"avg> as 
    # fourth element which we will use it as "All" option that each separated with newlines.

         split(stats, to_print, ",");
         # building an array called 'to_print' from 'stats' variable above with comma separator to distinguish 
         # the elements from each other.

         print to_print[select];
         # this will print the element which user-input as an option.
         # if input 1: will print 'min'
         # if input 2: will print 'max'
         # if input 3: will print 'avg'
         # if input 4: will print 'min' \n 'max' '\n' avg
    }' {} +
αғsнιη
источник
0

Как написано, скрипт ничего не делает, кроме как печатает популяции, потому что avg и т. Д. Закомментированы.

Для вычисления avg эти популяции должны быть отправлены в функцию avg () с чем-то вроде ...

echo "$(cat populations | avg)"

Подобные строки будут добавлены для min () и max ().

Вы можете использовать caseоператор для вызова соответствующей функции (ей) ...

  :
done
#
case s2
  1|4) echo "$(cat populations | min)" ;;&
  2|4) echo "$(cat populations | max)" ;;&
  3|4) echo "$(cat populations | avg)";;
esac
#
rm populations

1|4) echo ...Вызывает эхо работать , если либо 1 или 4 введены. Таким образом, все 3 будут выполнены, если 4 были введены.

DocSalvager
источник
1
PerlDuck - Вы правы (но это ';; &'). Исправленный. Благодарю.
DocSalvager
0

Спасибо за все ответы, вот что я закончил:

#!/bin/bash
### Returns the minimum value by sorting the population file's data and displaying the top line.
 function min () {
         echo "Minimum Population: "$(sort -n populations | head -1)
 }
  ### Returns the maximum value by sorting the population file's data and displaying the bottom line.
 function max () {
         echo "Maximum Population: "$(sort -n populations | tail -1)
 }
  ### A function to return the average number of population.
 function avg () {
         count=0
         sum=0
         while read line ; do
                 num=`echo ${line#* }`
                 sum=`expr $sum + $num`
                 count=`expr $count + 1`
         done < populations
         avg=`expr $sum / $count`
         echo "Average Population: "$avg
 }
  ### Advises what the script does and asks for an imput of a year.
 echo "
         ######################
         # Population adviser #
         ######################
          Please enter the year: "
 read s1
  ### If statement checking the year entered is available, if not then the user is informed of invalid selection and program terminates.
 if [[ $s1 -ge 2000 && $s1 -le 2019 && $s1 -ne 2009 ]] ; then
         continue 2>/dev/null
 else
         echo "The year you entered is not valid, program terminating"
         exit
 fi
  ### Prompts user for input
 echo "
         Enter an option:
         1. Minimum
         2. Maximum
         3. Average
         4. All
  -----(minimum) (maximum) (average) (all)-----
 "
 read s2
  ### Loops through all files within the given directory path and appends the population of each file to the population list
 for file in $(find ~/filesToSort/$s1 -type f) ; do
         tail -1 $file >> populations
 done
  ### If statement to validate user input and then use the function(s) required
 if [ "$s2" == "minimum" ] ; then
         min
 elif [ "$s2" == "maximum" ] ; then
         max
 elif [ "$s2" == "average" ] ; then
         avg
 elif [ "$s2" == "all" ] ; then
         min
         max
         avg
 else
         echo "The option you chose is invalid, program terminating"
         rm populations
         exit
 fi
  ### Removes "populations" file upon completion
 rm populations

При выборе варианта (1-4) вместо ввода цифр необходимо ввести слово, которое я ненавижу, но меня попросили сделать это таким образом.

Манты
источник