Bash для цикла продолжает добавлять поверх переменной - PullRequest
0 голосов
/ 04 ноября 2019

Цикл добавляет переменные, а не переписывает их. Я в растерянности относительно того, почему это происходит. Если кто-нибудь может мне помочь, то большое спасибо, что я новичок в bash, и я занимаюсь этим уже несколько часов ...

for (( i=1; i<=4; i++ ))
do
    echo User:$(tail -n $i /etc/passwd | cut --delimiter=":" --fields="1")
    echo
    echo
done

Вывод становится

User:art101c40
User:art101c39 art101c40
User:art101c38 art101c39 art101c40
User:art101c37 art101c38 art101c39 art101c40

Это должно выглядеть так

User: art101c40
User: art101c39
User: art101c38
User: art101c37

Ответы [ 2 ]

3 голосов
/ 04 ноября 2019

tail -n получает $i строки из конца файла, поэтому в первом цикле вы получите одну строку, во втором цикле вы получите две строки и т. Д. То, что вы хотите сделать, это получить последние четыре строкии итерируйте их, что вы можете сделать с помощью цикла while read:

tail -n 4 /etc/passwd |
    cut -d: -f1 |
    while read -r user  # "-r" prevents backslashes causing problems
do
    echo "User: $user"
done

Или вы можете использовать AWK для замены cut и цикла while read:

tail -n 4 /etc/passwd |
    awk -F: '{print "User:", $1}'

(с улучшениями от Гордон Дэвиссон )

1 голос
/ 04 ноября 2019

@ wjandrea уже объяснил, как это сделать;Я попытаюсь объяснить, почему оригинал не работал. Я также проиллюстрирую важную технику отладки для таких вещей: если сложный процесс не выполняет то, что вы хотите, разберите его на части и запустите отдельные части, и посмотрите, что они делают.

Вв этом случае первая итерация цикла работает так, как вы ожидаете (она печатает «User: art101c40»), поэтому давайте не будем об этом беспокоиться. Давайте посмотрим на вторую итерацию, где i = 2. Подставляя это значение, он запускает команду:

echo User:$(tail -n 2 /etc/passwd | cut --delimiter=":" --fields="1")

Итак, давайте пробежимся по частям и посмотрим, что они делают. Во-первых, он запускает tail -n 2 /etc/passwd, который напечатает последние две строки / etc / passwd. Примерно так:

art101c39:*:10139:99:Art 101 class 39:/home/art101c39:/bin/bash
art101c40:*:10140:99:Art 101 class 40:/home/art101c39:/bin/bash

Обратите внимание, что он печатает две последние строки (это то, что делает tail -n 2), поэтому он печатает как art101c39, так и art101c40. В этом корень всей проблемы.

Но давайте продолжим отслеживать ее поведение. Этот вывод передается по каналу cut --delimiter=":" --fields="1", который печатает:

art101c39
art101c40

, который захватывается $( ) для использования в качестве аргументов echo. Каждое из имен пользователей находится в отдельной строке, поэтому они разделены новой строкой, но $( ) не в двойных кавычках, поэтому он может быть разбит на слова. Разделение слов разбивает его на «слова» на основе пробелов (обычно пробелы, табуляции и новые строки), поэтому каждое имя пользователя просто становится отдельным словом. По сути, это становится "art101c39" и "art101c40". Если у любого из них были какие-либо подстановочные знаки в имени файла, оболочка попытается превратить их в списки соответствующих файлов;но здесь нет подстановочных знаков, поэтому этого не происходит. Эти "слова" затем добавляются в аргументы для echo, давая:

echo User:art101c39 art101c40

echo получает два аргумента: "User:art101c39" и "art101c40". Как всегда, он склеивает их вместе с пробелом между ними и печатает результат.

Подобные вещи происходят на третьей и четвертой итерациях, при tail печатаются три, а затем четыре счета и все этиучетные записи, проходящие через остальную часть обработки.

Это можно исправить напрямую, добавив head в конвейер: tail -n $i /etc/passwd | head -n 1 | .... При этом tail напечатает последние $i строки, а head удалит все, кроме первой из них ($i th от конца файла). Но этот подход имеет тенденцию быть неэффективным и (IMO) клугым. Это предполагает чтение всего файла, чтобы выделить одно поле из одной строки. Для четырех строк (учетных записей) вам необходимо прочитать весь файл четыре раза. Если вы собираетесь выбрать по два поля у каждого из этих пользователей, это будет означать чтение файла восемь раз.

Обычно есть более простой и лучший способ сделать это, но что это зависит от того, чтофактическая цель. Как правило, вы запускаете tail один раз, а затем распространенные способы работы с этим выводом включают: awk -F: делать всю работу;трубопровод к петле while IFS=: read -r user pw uid gid fullname homedir shell; do;использование readarray -t для вывода строк в массив оболочки, где вы можете работать с ними напрямую;и т.д.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...