středa 7. srpna 2024

Bash: Smyčka while read je smrtelná pro všechny asociativní pole (subshell)

 Toto zjištění vám může ušetřit celý den programování a množství problémů.

Ve smyčce

while read  ... do:

zde zpřístupni pole a ukládej do něj hodnoty

done

nepřežije pole, které jste nadeklarovali jako globální proměnnou pomocí declare -A

protože smyčka while read otevírá nový subshell. Po ukončení smyčky se ukončuje také shell, a to co vidíte na konci této smyčky už není to stejné pole, se kterým pracovala smyčka. Navenek se zdá že má stejný název, tak jde o to samé pole.

process_directories() {
    for dir in $(find "$source_path" -type d); do
        stats=$(stat -c '%U %G %A' "$dir")
        owner=$(echo "$stats" | awk '{print $1}')
        group=$(echo "$stats" | awk '{print $2}')
        rights=$(echo "$stats" | awk '{print $3}')

        owner_representation=$(get_owner_representation "$owner")
        group_representation=$(get_group_representation "$group")

        rights_short=""
        case "$rights" in
            "drwxr-xr-x") rights_short="D" ;;
            "drwx------") rights_short="d" ;;
            "drwxrwxr-x") rights_short="U" ;;
            "drwxrwxrwx") rights_short="s" ;;
            *) rights_short=$(stat -c '%a' "$dir") ;;
        esac

        echo "$owner_representation:$group_representation:$rights_short:0:0:$dir" >> "$dir_rights_file"
    done
}

"Výše jsou ve smyčce uvedeny dvě funkce, které mají vrátit a přiradit hodnotu. Podívají se nejprve jestli hodnota existuje v poli a když ne, tak ji tam přidají. Problém je, že toto je užitečné jen uvnitř té smyčky. Nelze to ty hodnoty pole externě zapsat do souboru až po ukončení smyčky. A zápis uvnitř smyčky je neefektivní.

Když jsem používal ChatGPT 4o k porovnávání dvou funkcí, původní funkce ve které to funguje, volané uvnitř té smyčky a kopie té funkce pojmenované jako test_fnc ale volaná externě, tak GPT napsal toto:

Rozumím vašemu problému a po dalším prozkoumání jsem zjistil, že rozdíl může být způsoben způsobem, jakým funkce get_owner_representation a test_fnc interagují s asociativním polem owners.

Hlavním rozdílem mezi voláním těchto funkcí uvnitř process_directories a mimo něj je kontext, ve kterém se spouští. Když používáte příkaz find s konstrukcí while read, nový subshell je vytvořen, což může ovlivnit viditelnost změn provedených ve vašem asociativním poli."

Takže jsem došel k jasnému závěru:

Změny, které byly provedeny v poli v rámci toho nově vytvořeného shellu nepřežijí ten shell proto funkce write_config_file nemá co zapisovat: process_directories # shell byl ukončen a změny provedené v poli vlastně byly provedeny do jiného pole, takže toto pole bylo ukončeno a neexistuje. Zatímco v následujícím kontextu existuje to staré pole beze změn. write_config_file

GPT následně navrhl toto nové řešení, ve které se nebude používat subshell:

Máte pravdu, že problém je způsoben tím, že find s while read běží v subshellu, takže změny provedené v asociativním poli v rámci tohoto subshellu se nepromítají zpět do rodičovského shellu. Řešením je vyhnout se použití subshellu nebo najít způsob, jak zajistit, aby změny byly zachovány.

Jednou z možností je použít jiný způsob iterace přes soubory a složky, který nevyžaduje použití subshellu. Můžeme nahradit find s while read cyklem for:

#!/bin/bash

# Definování souborů a proměnných
config_file="config.txt"
dir_rights_file="dir_rights.txt"
file_rights_file="file_rights.txt"
source_path="$1" # Získání cesty ze vstupního argumentu

# Písmena pro vlastníky a skupiny
owner_letters=(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)
group_letters=(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)

# Inicializace polí
declare -A owners
declare -A groups

# Funkce pro inicializaci polí
initialize_arrays() {
    for letter in "${owner_letters[@]}"; do
        owners[$letter]=""
    done

    for letter in "${group_letters[@]}"; do
        groups[$letter]=""
    done
}

# Funkce pro přiřazení písmena pro vlastníky
get_owner_representation() {
    local owner=$1
    local letter=${owner:0:1} # Získejte první písmeno vlastníka
    local representation=$letter

    echo "DEBUG: owner=$owner, letter=$letter, owners[$letter]=${owners[$letter]}"

    if [ -z "${owners[$letter]}" ]; then
        owners[$letter]=$owner
        echo "DEBUG: owners[$letter] was empty, now set to $owner"
    elif [ "${owners[$letter]}" != "$owner" ]; then
        representation=$owner # Pokud je již písmeno obsazeno jiným vlastníkem, použijte celý název
        echo "DEBUG: owners[$letter] was not empty and not equal to $owner, using full name"
    fi

    echo "$representation"
}

test_fnc() {
    local owner=$1
    local letter=${owner:0:1} # Získejte první písmeno vlastníka
    local representation=$letter

    echo "DEBUG: test_fnc - owner=$owner, letter=$letter, owners[$letter]=${owners[$letter]}"

    if [ -z "${owners[$letter]}" ]; then
        owners[$letter]=$owner
        echo "DEBUG: test_fnc - owners[$letter] was empty, now set to $owner"
    elif [ "${owners[$letter]}" != "$owner" ]; then
        representation=$owner # Pokud je již písmeno obsazeno jiným vlastníkem, použijte celý název
        echo "DEBUG: test_fnc - owners[$letter] was not empty and not equal to $owner, using full name"
    fi

    echo "$representation"
}

# Funkce pro zápis do konfiguračního souboru
write_config_file() {
    > "$config_file" # Vymazat existující soubor
    for letter in "${owner_letters[@]}"; do
        if [ -n "${owners[$letter]}" ]; then
            echo "$letter:${owners[$letter]}" >> "$config_file"
        else
            echo "$letter:" >> "$config_file"
        fi
    done

    for letter in "${group_letters[@]}"; do
        if [ -n "${groups[$letter]}" ]; then
            echo "$letter:${groups[$letter]}" >> "$config_file"
        else
            echo "$letter:" >> "$config_file"
        fi
    done
}

# Funkce pro zpracování složek a zápis práv
process_directories() {
    for dir in $(find "$source_path" -type d); do
        stats=$(stat -c '%U %G %A' "$dir")
        owner=$(echo "$stats" | awk '{print $1}')
        group=$(echo "$stats" | awk '{print $2}')
        rights=$(echo "$stats" | awk '{print $3}')

        owner_representation=$(get_owner_representation "$owner")
        group_representation=$(get_group_representation "$group")

        rights_short=""
        case "$rights" in
            "drwxr-xr-x") rights_short="D" ;;
            "drwx------") rights_short="d" ;;
            "drwxrwxr-x") rights_short="U" ;;
            "drwxrwxrwx") rights_short="s" ;;
            *) rights_short=$(stat -c '%a' "$dir") ;;
        esac

        echo "$owner_representation:$group_representation:$rights_short:0:0:$dir" >> "$dir_rights_file"
    done
}

print_associative_array() {
    local -n arr=$1
    for key in "${!arr[@]}"; do
        echo "$key: ${arr[$key]}"
    done
}

clear
# Hlavní část skriptu
initialize_arrays
echo "After initialize_arrays: print owners"
print_associative_array owners

# Zavolejte get_owner_representation před voláním process_directories, abyste nastavili hodnotu pro "root"
get_owner_representation "root"

process_directories

echo "process_directories: call test_fnc"
test_fnc "david"

echo "After test_fnc: print owners"
print_associative_array owners

write_config_file

echo "Configuration file has been created: $config_file"
echo "Directory rights file has been created: $dir_rights_file"

 

PŮVODNÍ KÓD KTERÝ NEFUNGOVAL SPRÁVNĚ

#!/bin/bash

# Definování souborů a proměnných
config_file="config.txt"
dir_rights_file="dir_rights.txt"
file_rights_file="file_rights.txt"
source_path="$1" # Získání cesty ze vstupního argumentu

# Písmena pro vlastníky a skupiny
owner_letters=(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)
group_letters=(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)

# Inicializace polí
declare -A owners
declare -A groups

# Funkce pro inicializaci polí
initialize_arrays() {
    for letter in "${owner_letters[@]}"; do
        owners[$letter]=""
    done

    for letter in "${group_letters[@]}"; do
        groups[$letter]=""
    done
}

test_fnc() {
    local owner=$1
    local letter=${owner:0:1} # Získejte první písmeno vlastníka
    local representation=$letter

    if [ -z "${owners[$letter]}" ]; then
        owners[$letter]=$owner
        echo "A:${owners[$letter]} it was set '$owner'"
    elif [ "${owners[$letter]}" != "$owner" ]; then
        representation=$owner # Pokud je již písmeno obsazeno jiným vlastníkem, použijte celý název
    fi

    echo "$representation"
}

# Funkce pro přiřazení písmena pro vlastníky
get_owner_representation() {
    local owner=$1
    local letter=${owner:0:1} # Získejte první písmeno vlastníka
    local representation=$letter

    echo "DEBUG: owner=$owner, letter=$letter, owners[$letter]=${owners[$letter]}"

    if [ -z "${owners[$letter]}" ]; then
        owners[$letter]=$owner
        echo "DEBUG: owners[$letter] was empty, now set to $owner"
    elif [ "${owners[$letter]}" != "$owner" ]; then
        representation=$owner # Pokud je již písmeno obsazeno jiným vlastníkem, použijte celý název
        echo "DEBUG: owners[$letter] was not empty and not equal to $owner, using full name"
    fi

    echo "$representation"
}


# Funkce pro přiřazení písmena pro skupiny
get_group_representation() {
    local group=$1
    local letter=$(echo "${group:0:1}" | tr '[:lower:]' '[:upper:]') # Získejte první písmeno skupiny a převedení na velké písmeno
    local representation=$letter
    if [ -z "${groups[$letter]}" ]; then
        groups[$letter]=$group
    elif [ "${groups[$letter]}" != "$group" ]; then
        representation=$group # Pokud je již písmeno obsazeno jinou skupinou, použijte celý název
    fi

    echo "$representation"
}

# Funkce pro zápis do konfiguračního souboru
write_config_file() {
    > "$config_file" # Vymazat existující soubor
    for letter in "${owner_letters[@]}"; do
        if [ -n "${owners[$letter]}" ]; then
            echo "$letter:${owners[$letter]}" >> "$config_file"
        else
            echo "$letter:" >> "$config_file"
        fi
    done

    for letter in "${group_letters[@]}"; do
        if [ -n "${groups[$letter]}" ]; then
            echo "$letter:${groups[$letter]}" >> "$config_file"
        else
            echo "$letter:" >> "$config_file"
        fi
    done
}

# Funkce pro zpracování složek a zápis práv
process_directories() {
    find "$source_path" -type d | while IFS= read -r dir; do
        # owner=$(stat -c '%U' "$dir")
        # group=$(stat -c '%G' "$dir")
        # rights=$(stat -c '%A' "$dir")
        stats=$(stat -c '%U %G %A' "$dir")
        owner=$(echo "$stats" | awk '{print $1}')
        group=$(echo "$stats" | awk '{print $2}')
        rights=$(echo "$stats" | awk '{print $3}')

        # echo "Before test_fnc: ${owners[@]}"
        # test_fnc "$owner"
        # echo "After test_fnc: ${owners[@]}"
        # sleep 20

        # Přiřaďte písmeno pro vlastníka
        get_owner_representation "root"
        owner_representation=$(get_owner_representation "$owner")

        # Přiřaďte písmeno pro skupinu
        group_representation=$(get_group_representation "$group")

        # Zjištění práv
        rights_short=""
        case "$rights" in
            "drwxr-xr-x") rights_short="D" ;;
            "drwx------") rights_short="d" ;;
            "drwxrwxr-x") rights_short="U" ;;
            "drwxrwxrwx") rights_short="s" ;;
            *) rights_short=$(stat -c '%a' "$dir") ;;
        esac

        echo "$owner_representation:$group_representation:$rights_short:0:0:$dir" >> "$dir_rights_file"
    done
}

print_associative_array() {
    local -n arr=$1
    for key in "${!arr[@]}"; do
        echo "$key: ${arr[$key]}"
    done
}


# Hlavní část skriptu
initialize_arrays
echo "After initialize_arrays: print owners"
print_associative_array owners
process_directories
echo "process_directories: call test_fnc"
test_fnc "david"
echo "After test_fnc: print owners"
print_associative_array owners

write_config_file

echo "Configuration file has been created: $config_file"
echo "Directory rights file has been created: $dir_rights_file"


Žádné komentáře:

Okomentovat

Jak zkopírovat styly jako font a odstavec v LibreOffice Write?

V dokumentu Write píšu CV. Někde uprostřed mám nadpisy a chci zkopírovat jeden nadpis v četně stylů a vložit ho jinde. Když použiju např ct...

Štítky

.profile adm AI alfa transparence AND any aplikace asociativní pole atomicity audio awk bash benchmark bezpečnost biblehub BJT boolean buffering Cache-Conrol Cloudflare code Collector Cut-off colorpicker compare cookies css CSS3 curl cut čas data loss data lost data transfer reliability datasheet datetime.strptime development dioda diody EBO Emitter Cut-off Current ETag exclude exec Expires fflock fflush ffmpeg file read file write file_get_contents file_get_contents/file_put_contents file_put_contents filter find first_install.sh flock Fly-back dioda font-face fóra fotorezistor fread functions funkce fwrite gate gate drive geolokace gradient-background grep grub grub update hebrejština history hlavičky HS html html 5 https hudba charakterizace chroot ICES IGBT img sizes img srcset impedance implementace imshow inference inrush current install jalový výkon javascript javescript jednocení seznamů js kapacita součástek koeficient zesílení komunikace se serverem konfigurace Krita KSF Last-Modified lazy caching led LEFT JOIN librosa ligatury linux list log manuál masky matplotlib Max-Age measure memory měření MFCC MFCC koeficienty mint míry modules moralizace morphologie MOSFET mount moviepy mysql náběhový proud napěťová ochrana návod nel Network Error Logging NLP not nth-child oblékání ochrana okruhy přátel OpenVINO IR formát optočlen ořezové masky OSHB otázky otázky_jazyky otázky_moralismu_řešení parsování path personifikace photorec php php 4 php 5 php 6 php 7 php 8 phpbb phpBB3 pitch PN přechody pnp pole práva profilování program prune průraz přepěťová ochrana přepolování pseudokódd PWM regulátory pydub python python3 pytorch RBE RDSon read reaktance rectifier regulace vstupního napětí relyability remount replace restore reverzní geolokace RIGHT JOIN rm role rozvržení disků pro OS linux a data databází řešení samba sdílení Sec-Fetch-Dest Sec-Fetch-Mode Sec-Fetch-Site Sec-Fetch-User Secure Shell sed Set Cookie shunt schottka skript sloupce song sort soubory soundfile spínané zdroje spínání split SQL ssh stabilizace napětí stahování stream string strojové učení stropové učení syntax T5 tabulky tepelná ztráta test text-shadow thermal runaway time timestamp tkinter tr transistor transition tranzistor tranzistory tuple tvorba otázek TVS účiník update va charakteristika Vgs video Vth vynechání adresářů vývoj while wrapovací funkce XOR zdánlivý výkon zdroj zenerka zenerovo napětí zip zip archiv zkratky zpomalení zpracování textu Žalmy