[Bash] xattr script refactor

Pagina: 1
Acties:

  • X-Lars
  • Registratie: Januari 2004
  • Niet online

X-Lars

Just GoT it.

Topicstarter
Hoi,

Sinds ik een Mac heb, ben ik ook wat aan het scripten geslagen in bash. Dit vnl. om alledaagse taken te automatiseren en zeker ook ter leering ende vermaeck. Zo heb ik hier een script dat alle extended attributes verwijdert, in de huidige directory of recursive. Of "verbose" die de output niet naar /dev/null stuurt (voor het debuggen) - althans dat zou het moeten doen (ik wil wel mijn eigen "echo" commands als output zien, maar niet de command output).

code:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
## Delete extended attributes (in current, and sub-folders)

delattrs() {

    # -r = apply recursively
    if [ $# > 0 -a "$1" = "-r" ]; then
        local recursiveLs="ls -l@R";
    else
        local recursiveLs="ls -l@";
    fi

    for i in $(${recursiveLs} | grep '^ ' | awk '{print $1}' | sort -u);
        do

        # -r = apply recursively
        if [ $# > 0 -a "$1" = "-r" ]; then
            local recursiveXattrs="-r ";
        else
            local recursiveXattrs="";
        fi

        echo "Removing $i"

        # -v = verbose output
        if [ $# > 0 -a "$1" = "-v" ]; then
            sudo xattr -d $recursiveXattrs $i *
        else
            sudo xattr -d $recursiveXattrs $i * > /dev/null;
        fi
    done
}


Wat de code doet:
  1. een lijst genereren van extended attributes (die hebben een tab als prefix in de extended ls listing)
  2. over die extended attributes itereren en die in alle files (evt. recursief) verwijderen
Hier schort erg veel aan, en ik zou graag leren hoe dat beter kan. Hier zijn wat punten die volgens mij niet goed zijn:
  1. Zoiets als "parameter handling", het gebruik van parameters wordt niet flexibel opgelost en ik kan nu zelfs maar 1 parameter tegelijk gebruiken (-r of -v). Een heleboel if-then constructies lijkt me niet de bedoeling.
  2. Het gebruik van -r in ls en -R in xattr is inconsistent vanuit deze commands zelf, maar hoe zou ik daar beter mee om kunnen gaan?
  3. De command xattr lijkt zich niets aan te trekken van > /dev/null: hij geeft een error als xattr een bepaalde attribute niet vindt.
  4. Hij geeft als file parameter voor xattr een "*", idealiter zou in plaats daarvan precies de files gegeven worden die dat extended attribute hebben.
Weten jullie hoe het beter kan?

  • sam.vimes
  • Registratie: Januari 2007
  • Laatst online: 08-06 08:44
Hier wat raadgevingen aangaande bash-scripting.
  1. Voor parameter handling onder bash is de built-in getopts de standaard methode.
    Stukje uit een script van mezelf:
    Bash:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    
    while getopts 'd:z' opt
    do
        case $opt in
        d)  prevdate=$OPTARG ;;
        z)  compress=1 ;;
        *)  exit 1 ;;
        esac
    done
    shift $((OPTIND-1))

    Het programma kent de opties -d en -z. De de ':' achter de d betekent dat optie -d een argument krijgt, de optie -z is een switch. Omdat getopts een built-in is, kan hij shellvariabelen wijzigen, in dit geval krijgt de variabele opt steeds de volgende optieletter die in het case-statement gebruikt wordt. OPTARG krijgt de waarde van het optieargument en OPTIND is de index van het aan de beurt zijnde argument. De shift aan het eind verwijdert de afgehandelde opties uit de argumentenlijst, zodat je daarna alleen nog maar parameters over hebt die geen opties zijn.
    Voorbeeld van een aanroep:
    code:
    1
    
    ./mijnscript -z -d 2009-12-05 -- file1 file2
    • Het argument 2009-12-05 mag aan de -d vast zitten of er los van staan
    • De opties -z en -d mogen worden samengenomen: -zd2009-12-05
    • De -- geeft het einde van de opties aan en is alleen noodzakelijk als de eerstvolgende parameter met een - begint (anders wordt die gezien als optie).
  2. Als je recursieve lijsten van files wilt verwerken, gebruik dan find en niet ls -R. De output van de laatste is moeilijk te parsen
  3. Vermijd commando-interpolatie $(...) als je niet van tevoren weet hoe groot de lijst wordt. Linux heeft een maximale commandoregellengte van een megabyte of zoiets (en andere unixen veel minder). Met een recursieve directorylisting zit je daar snel aan.
  4. Het script houdt kennelijk geen rekening met filenamen met spaties.
  5. Als je combinaties van grep en awk hebt, kun je die samennemen in één awk-commando:
    code:
    1
    
    grep PATTERN | awk '{doeiets}'

    is vrijwel hetzelfde als
    code:
    1
    
    awk '/PATTERN/ {doeiets}'

    maar scheelt een overbodige commando-aanroep.
  6. GNU find kent de optie -maxdepth om het recursielevel te beperken. Gebruik -maxdepth 1 om alleen files in de huidige directory te processen.
  7. de redirection >/dev/null stuurt alleen standard output naar de prullenbak. Om ook stderr (file descriptor 2) daarheen te sturen moet je 2>/dev/null toevoegen. Pas op: zinvolle foutmeldingen mis je op die manier ook.
    Dit kun je opvangen met:
    code:
    1
    
    xattr -d "$eenfile" 2>/dev/null || echo xattr lukte niet bij "$eenfile"
  8. Neem 'sudo' niet op in je script, maar roep je script aan met sudo. Dan werkt het ook voor minder machtige gebruikers, als die het op hun eigen files willen toepassen. Bovendien heeft de 'find' of 'ls' dan ook root-rechten, zodat je om te beginnnen alle files kunt vinden.
De structuur van je programma zal er ongeveer als volgt uitzien:
Bash:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
#!/bin/bash

norecurse='-maxdepth 1'
typeset -i verbose=0

while getopts rv opt
do
    case $opt in
    r) unset norecurse ;;
    v) verbose=1 ;;
    *) exit 2 ;;
    esac
done
shift $((OPTIND-1))

# Dit programma kan geen filenamen met newlines verwerken.
find . $norecurse | while read file
do
    if ((verbose))
    then
        # Let op de dubbele quotes om "$file"
        echo doe iets met "$file"
    fi
done


Ik weet niet of jouw Mac ook de GNU find heeft, of dat het de ouderwetse Unix find is (ik heb geen Mac). Zie man find voor de complete lijst van mogelijkheden.

"ouderwetse" find:
Omdat ik vermoed dat het xattr-commando wel meerdere filenamen op de regel aankan, zou je gebruik kunnen maken van xargs.
code:
1
2
# niet recursief
ls -A | xargs xattr -d --

code:
1
2
# recursief
find . | xargs xattr -d --


De -- is om ook filenamen die beginnen met een '-' correct te behandelen.

GNU find:
Omdat ik vermoed dat het xattr-commando wel meerdere filenamen op de regel aankan, zou je gebruik kunnen maken van de nieuwe optie
code:
1
-exec ... {} +
van GNU find.
code:
1
find . -maxdepth 1 -exec xattr -d -- {} +


Zowel xargs als -exec ... + stellen uit de gelezen/gevonden filenamen een commandoregel samen die zo lang mogelijk is zonder de maximale commandolengte te overschrijden.

  • X-Lars
  • Registratie: Januari 2004
  • Niet online

X-Lars

Just GoT it.

Topicstarter
Zeer hartelijk bedankt, sam.vimes! Daar ga ik morgen eens lekker mee bezig. Hopelijk kan ik dan een mooi resultaat posten :-)

  • X-Lars
  • Registratie: Januari 2004
  • Niet online

X-Lars

Just GoT it.

Topicstarter
De pijn zit hem vooral in het feit dat je met find geen extended attributes (xa) kunt vinden.

Hier twee sub-optimale scripts.

De eerste itereert over de files die een xa hebben, en verwijdert vervolgens alle gevonden xa's (dus niet per file). Wat hier zeker niet goed aan is, is de expressie in het for statement die telkens opnieuw wordt uitgevoerd (ls | awk | sort):
code:
1
2
3
4
5
6
7
8
9
10
#!/bin/bash

ls -@ | while read file
    do
        for x in $(ls -lA@ | awk '/^    / {print $1}' | sort -u);
            do
                echo "Removing $x from $file"
                xattr -d $x $file
            done
    done


Het tweede script itereert over alle gevonden xa's (als in OP) en verwijdert de xa in alle files:
code:
1
2
3
4
5
6
7
#!/bin/bash

for i in $(ls -lA@ | awk '/^    / {print $1}' | sort -u);
    do
        echo "Removing $i"
        find . -print0 | xargs -0 -t -I {} xattr -d -- $i {}
    done


Ben er wel een stuk wijzer op geworden ondertussen (bijv. de -print0/-0, -I en -t switches/args etc.), maar voor het specifieke probleem (efficient alle exended attributes verwijderen) heb ik nog geen mooie oplossing. Volgens mij ligt een effecientere oplossing in het parsen van de output van
code:
1
ls -@lA
Alleen die output is dan weer "multi-line" per gevonden item.

Any help greatly appreciated :-)

  • sam.vimes
  • Registratie: Januari 2007
  • Laatst online: 08-06 08:44
X-Lars schreef op zondag 06 december 2009 @ 15:45:
Volgens mij ligt een effecientere oplossing in het parsen van de output van
code:
1
ls -@lA
Alleen die output is dan weer "multi-line" per gevonden item.
Kun je een voorbeeld posten hoe die output eruit ziet? Ik heb geen Mac, en op Linux wordt de optie -@ van ls niet herkend.

  • X-Lars
  • Registratie: Januari 2004
  • Niet online

X-Lars

Just GoT it.

Topicstarter
Uiteraard...

code:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
-rw-r--r--@  1 lars  staff   65969402 Nov 17 10:45 VirtualBox-3.0.12-54655-OSX.dmg
    com.apple.diskimages.fsck          20 
    com.apple.diskimages.recentcksum           80 
    com.apple.metadata:kMDItemWhereFroms          173 
    com.apple.quarantine           78 
drwxr-xr-x@ 42 lars      wheel      1428 Dec  7 21:56 gnupg-1.4.10
    com.apple.quarantine          42 
-rw-r--r--@  1 lars      staff  18237440 Dec  7 21:50 gnupg-1.4.10.tar
    com.apple.quarantine          42 
-rw-r--r--@ 1 lars  staff  114408 Nov 28 14:40 28112009329-df.jpg
    com.apple.FinderInfo        32 
    com.apple.ResourceFork   86628 
-rw-r--r--  1 lars  staff  596009 Nov 28 14:37 28112009329.jpg
-rw-r--r--  1 lars  staff  678843 Nov 28 14:37 28112009330.jpg
-rw-r--r--  1 lars  staff  619650 Nov 28 14:37 28112009331.jpg
-rw-r--r--  1 lars  staff  664848 Nov 28 14:36 28112009332.jpg
drwxr-xr-x  9 lars  staff     306 Dec  6 14:04 MP Navigator EX
-rw-r--r--@ 1 lars  staff   92522 Nov 28 14:52 foto-1-df.jpg
    com.apple.FinderInfo        32 
    com.apple.ResourceFork   81240

  • sam.vimes
  • Registratie: Januari 2007
  • Laatst online: 08-06 08:44
Aha, nou snap ik het. De
code:
1
awk '/^    /{print $1}'
is dus bedoeld om alle aanwezige extended attributen te vinden en die worden vervolgens verwijderd van alle files. En omdat niet alle files alle attributen hebben, worden er door xattr foutmeldingen gegenereerd op het moment dat er niet bestaande attributen worden verwijderd.
Ik denk dat het daarom beter is file voor file te behandelen en precies de extended attributen te verwijderen die op de file zitten. Dan krijg je geen ongewenste foutmeldingen en zullen de meldingen die je wel krijgt echt "belangrijk" zijn (voor een zekere waarde van belangrijk).
Volgens http://xattr.sourceforge.net/ kan er maar 1 attribuut per aanroep van xattr worden verwijderd. We krijgen dus twee geneste lussen: een voor de files en een voor de attributen van een file.
Bash:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
#!/bin/bash
# Zelf het goede 'find' commando nog opgeven (met topdirectory en -maxdepth).
# 'read' leest per regel en kan dus geen filenamen aan met embedded newlines.
# 'ls -d' voorkomt het listen van de *inhoud* van directory's.
# 'NR>1' (bij awk) slaat de eerste regel met de naam van de file over.
find | while read file
do
    # attribuutnamen staan vanaf regel 2 en bevatten geen spaties
    XATTRS=$(ls -d@ -- "$file" | awk 'NR>1{print$1}')
    for a in $XATTRS
    do
        xattr -d $a -- "$file"
    done
done

Overigens denk ik dat het opvragen van de aanwezige attributen op een file eenvoudiger kan met
code:
1
xattr -- "$file"
(i.p.v. met ls -@d).
Zoals gezegd, bij gebrek aan een Mac kan ik het niet proberen, maar ik neem aan dat dit commando precies de aanwezige attributen op de file geeft, één per regel. In dat geval kan de pipe naar awk vervallen.
code:
1
XATTRS=$(xattr -- "$file")

[ Voor 14% gewijzigd door sam.vimes op 08-12-2009 15:02 . Reden: xattr zonder opties ipv ls ]


  • X-Lars
  • Registratie: Januari 2004
  • Niet online

X-Lars

Just GoT it.

Topicstarter
Ja, en nu zie ik pas dat je gewoon met
code:
1
xattr [-r] *
alle extended attributes krijgt incl. file name. Met -r voor recursive.

Dit is dus precies wat ik zoek:
code:
1
xattr * | awk -F": " '{print "xattr -d "$2" "$1""}' | sh


Nieuw voor mij zijn de -F parameter van awk en de laatste "sh" pipe. Weet niet of dat efficient is overigens,

Ten eerste werd ik op het verkeerde been gebracht door dit artikel over removing extended attributes. Vervolgens moet ik over de eerste regel van de xattr help hebben gelezen, en heb me beperkt tot ls -@. Het goede nieuws is dat ik er veel van opgestoken heb! :-)

Thanks, sam.vimes

  • X-Lars
  • Registratie: Januari 2004
  • Niet online

X-Lars

Just GoT it.

Topicstarter
Mmm... kom er wel achter dat whitespace in de file name zorgt voor problemen. Dit is beter:

code:
1
xattr -r * | awk -F": " '{print "xattr -d "$2" \""$1"\""}' | sh


Eventueel kan ook de $2 extra escaped worden t.b.v. whitespace.
Pagina: 1