Есть ли способ указать последнюю итерацию в цикле по строкам входного файла?

Я нашел решение, которое мне подходит.

Сначала я изучил исходники mwm, libXm, libX11, чтобы понять, как заставить программы использовать курсоры меньшего размера. Я попробовал некоторые изменения, но понял, что это потребует много работы, перекомпиляции и т. д.

Затем я переименовал/удалил каталог /usr/share/icons/Adwaita/cursors. Когда X больше не находил курсоры увеличенного размера, установленные пакетом adwaita-cursor-theme, единственные оставшиеся не так красивы, как курсоры Adwaita, но по крайней мере имеют правильный размер.

Я считаю, что курсоры adwaita требуются для основных пакетов, поэтому я не могу позволить себе удалить пакет adwaita-cursor-theme. У меня есть два варианта:

  1. Удалить каталог курсоров и использовать курсоры не из этой темы. Остальные курсоры, вероятно, из шрифта курсора, который поставляется с Xorg. У них всего два цвета, а у курсоров Adwaita больше цветов и теней.
  2. Изменение размера курсоров Adwaita, что легко сделать, например, с помощью Gimp.

Окончательное решение:Я скачал исходники темы adwaita и создал новые курсоры. Есть файлы *.in, в которых указаны размеры курсора. Я отредактировал эти файлы, чтобы удалить курсоры большого размера, и создал новые курсоры с make.sh, что в основном вызывает xcursorgenдля каждого курсора. Я поместил эти курсоры в /usr/share/icons/Adwaita/cursors, и теперь у меня есть курсоры нужного мне размера.

11
12.10.2021, 09:49
8 ответов

Цикл не может знать, когда он дойдет до конца, если он действительно не дойдет до конца. Таким образом, либо вы обрабатываете свой файл дважды, один раз, чтобы получить количество строк, а другой — для вывода, либо вы печатаете предыдущее значение на каждой итерации, а последнее — при выходе из цикла :

.
prev=""
while read direction; do 
    if [ -n "$prev" ]; then 
        echo "Direction: $prev"
    fi
    prev="$direction"
done < direction
echo "Last Direction: $prev" 
16
12.10.2021, 10:01

Вы можете считать значения в массив, а затем использовать длину массива:

#!/bin/bash
i=0
while read line ; do
  arr[$i]="$line"
  ((i++))
done <file

for (( i=0 ; i<=${#arr[@]}-2 ; i++ )) ; do
  echo Direction: ${arr[i]}
done
echo Last direction: ${arr[-1]}

Конечно, это означает, что все данные должны находиться в оперативной памяти.

Более простой arr=( $(cat file) )будет работать только в том случае, если строки не содержат пробелов, поэтому здесь я использовал цикл while read-.

7
12.10.2021, 10:39

Вы можете использовать сочетание paste, tailи head:

.
$ paste -s -d'\n' <(head -n -1 direction | sed 's/^/Direction: /') <(tail -n1 direction | sed 's/^/Last direction: /')
Direction: east
Direction: north
Direction: south
Direction: west
Last direction: south-west

  • paste -s -d'\n'использует новую строку в качестве разделителя с опцией -d'\n'и опцией-s:

    -s, --serial
    paste one file at a time instead of in parallel

  • head -n -1извлекает все строки, кроме последней, затем передается в sedдля выполнения нужной команды только в этих строках.

  • tail -n1 fileизвлекает последнюю строку файла, затем передается в sedдля выполнения нужной команды только в этой строке.


Или, более кратко, просто используя catвместо paste, как отмечено в комментарии @Mick Matteo:

$ cat <(head -n -1 direction | sed 's/^/Direction: /') <(tail -n1 direction | sed 's/^/Last direction: /')
5
12.10.2021, 12:42

Вариант ответа FelixJN , чтение строк в массив, но с использованием встроенных функций bash вместо циклов bash:

mapfile -t DIR < direction
printf "Direction: %s\n" "${DIR[@]::${#DIR[@]}-1}"
printf "Last direction: %s\n" "${DIR[-1]}"

mapfile — это встроенная функция, добавленная в Bash 4, которая считывает стандартный ввод в массив с одной строкой на запись. Опция -tудаляет символы новой строки.

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

10
12.10.2021, 19:54

Расширение ответов FelixJN и Ника Маттео:с использованием переносимых функций оболочки (с учетом того, что вопрос не имеет тегов, специфичных для оболочки -)и предполагая, что вы обрабатываете контролируемый, «достаточно маленький -» набор строк, вы можете узнать, когда цикл достигает своего последнего элемента, если поместите данные в массив позиционных параметров:

IFS='
'
set -f
set -- $(cat direction)
for dir
do
  if [ $(( i = i + 1 )) -eq "$#" ]
  then
    printf '%s' 'Last direction: '
  else
    printf '%s' 'Direction: '
  fi
  printf '%s\n' "$dir"
done

Имейте в виду, что вы можете захотеть правильно восстановитьIFSпосле его установки.

Обратите внимание, однако, что это удалит все пустые входные строки из вывода. Если вас это не устраивает, вы можете загрузить массив позиционных параметров в цикле:

set --
while IFS= read -r line
do
  set -- "$@" "$line"
done <direction

(Нет необходимости устанавливать IFSна новую строку и set -fв данном случае ).


Подход из ответов cas(sedpart )и terdon — обработка последнего элемента «вне» цикла — также может быть реализован в AWK с использованием довольно распространенного шаблона:

awk '
  NR > 1 { print "Direction: " dir }
  { dir = $0 }
  END { if (NR > 0) print "Last direction: " dir }
' direction
3
12.10.2021, 23:15
col=`awk 'END{print NR}' filename`
awk -v co="$col" '{if (NR<co){print "Direction: "$0}else{print "Last direction: "$0}}' filename

output

Direction: east
Direction: north
Direction: south
Direction: west
Last direction: south-west
0
15.10.2021, 13:06

Один из способов — зациклиться на строках и отложить печать строк на одну итерацию :мы печатаем предыдущую строку, сохраненную из переменной(prevв коде ниже ), а затем сохраняем текущую строку в этом потом переменная. После завершения цикла у нас есть последняя строка в prev, и мы можем вывести ее со специальным префиксом :

.
#! /bin/sh
prev=""
first="yes"
# from https://stackoverflow.com/a/1521498/648741
while IFS="" read -r p || [ -n "$p" ]; do
  if [ "$first" = "yes" ]; then
    first="no"
  else
    printf 'Direction: %s\n' "$prev"
  fi
  prev="$p"
done < directions
if [ "$first" = "no" ]; then
    printf 'Last direction: %s\n' "$prev"
fi

Код здесь уделяет особое внимание тому, чтобы не повредить пробелы и разрешить знаки доллара в строках. Если в этом нет необходимости, работает и более простой вариант:

#! /bin/sh
prev=""
while read -r p; do
  if [ -n "$prev" ]; then
    echo "Direction: $prev"
  fi
  prev="$p"
done < directions
if [ -n "$prev" ]; then
    echo "Last direction: $prev"
fi
0
15.10.2021, 16:18

Использование Raku (, ранее известного как Perl _6)

raku -e 'my $fh = $*ARGFILES.IO.open; $fh.eof ?? put("Last direction: $_") !! put("Direction: $_") for $fh.lines; $fh.close;' file.txt

ИЛИ

raku -e 'my $fh = $*ARGFILES.IO.open; for $fh.lines() { if not $fh.eof { put "Direction: $_" } else { put "Last direction: $_" };}; $fh.close;' file.txt

Пример ввода:

east
north
south
west
south-west

Пример вывода:

Direction: east
Direction: north
Direction: south
Direction: west
Last direction: south-west

В настоящее время Raku реализует метод .eof, который работает с объектами IO::Handleили IO::CatHandleи "...возвращает True, если операции чтения исчерпали содержимое дескриптора...."

В первом примере кода выше используется тернарный оператор Раку $condition ?? $true !! $falseдля проверки того, является ли $fh.eofTrue. Обратите внимание выше для обоих примеров кода, как .eofработает непосредственно со скаляром дескриптора файла $fh, тогда как $_загружается с извлеченными (строковыми -мудрыми )данными, которые печатаются с использованиемput(или say, при желании ).

Обратите внимание, что приведенные выше довольно упрощенные примеры кода работают только с одним файлом за раз в командной строке bash. Однако вполне вероятно, что передача списка файлов в $fh(, например, с помощью метода Раку dir()), может быть лучшим способом манипулирования несколькими файлами одновременно.

https://docs.raku.org/routine/eof
https://docs.raku.org/language/operators#index-entry-operator_ternary
https://raku.org

0
20.10.2021, 03:12

Теги

Похожие вопросы