[bash] Argumenten doorgeven aan alias die subshell opent

Pagina: 1
Acties:

Acties:
  • 0 Henk 'm!

Anoniem: 474628

Topicstarter
Ok, ik weet dat dit heel eenvoudig zou moeten zijn, maar na een uurtje googelen en verschillende soorten, maten en aantallen quotes proberen, ben ik de weg helemaal kwijt.

Ik heb een alias / functie in /.bash_aliases:
C:
1
2
3
4
#!/bin/bash
preload () {
  bash -c "source /opt/software/settings.sh && "$@""
}


De bedoeling is dat de preload-functie een subshell opent en daarin de settings sourced en dan het overgebleven programma uitvoert met eventuele argumenten. Ik wil namelijk absoluut niet de settings gesourced hebben in de huidige open shell! Dus ik gebruik het bijvoorbeeld zo:
C:
1
~$> preload software project.file


Het rare is dat "software" wel opent, maar project.file volgens mij niet meekrijgt, want hij opent het project niet. Als ik gewoon in mijn shell eerst het source commando uitvoer en vervolgens alleen "software project.file" in type werkt het wel prima.

De uitkomst van
C:
1
2
3
preload_test () {
  echo bash -c "source /opt/software/settings.sh && "$@""
}

is wel
bash -c source /opt/software/settings.sh && sofware project.file
Dus dat ziet er op zich goed uit. Wat gaat hier mis? Ik heb al geprobeerd $@ niet te quoten, maar dat geeft hetzelfde resultaat: het project wordt niet automatisch geopend. Ook heb ik geprutst met enkele, en dubbel dubbele quotes en escapes, etc...

In plaats van $@ kan ik misschien $* gebruiken, maar dat gaat fout als je strings meegeeft in de shell (zoiets als " preload software project.file "long text argument" "), want dat wordt dat argument dat gequote is alsnog opgebroken in losse woorden.

Bedankt!

Acties:
  • 0 Henk 'm!

  • deadinspace
  • Registratie: Juni 2001
  • Laatst online: 06-07 15:56

deadinspace

The what goes where now?

Anoniem: 474628 schreef op dinsdag 12 maart 2013 @ 13:38:
C:
1
2
3
4
#!/bin/bash
preload () {
  bash -c "source /opt/software/settings.sh && "$@""
}
Je kunt quotes niet nesten (Nouja, wel als je " en ' mixt, maar dat werkt hier niet). Bash ziet dit dus als "source /opt/software/settings.sh && " gevolgd door $@ gevolgd door "". Je $@ is dus niet gequote. Als $@ dan "software project.file" (als twee losse "woorden") bevat, dan krijg je dus:

bash -c "source /opt/software/settings.sh && "software project.file""


Wat neerkomt op
bash -c <source /opt/software/settings.sh && software> <project.file>

Waar <bla> één argument is. "project.file" wordt dus niet meer gezien als argument van -c.

Om dit te bereiken moet je $@ inderdaad quoten, maar dat gaat dus (vziw) niet binnen een andere gequote string. De makkelijkste oplossing lijkt me in dit geval om niet expliciet bash aan te roepen voor een subshell, maar om () te gebruiken:
C:
1
2
3
4
#!/bin/bash
preload () {
  (source /opt/software/settings.sh && "$@")
}

Acties:
  • 0 Henk 'm!

Anoniem: 474628

Topicstarter
Dat leek in eerste instantie te werken, maar nu krijg ik niet de juiste output van het settings script en veranderd die PATH niet. Dus mijn uitkomst is nu:

$> preload software project.file
software: command not found


Terwijl het iets moet zijn van:

$> preload software project.file
Loading script 1 ...
Loading script 2 ...
Setting PATH .
< software start >


Het grappige is dat als ik alleen preload zonder argumenten aanroep, ik wel de output van settings.sh voorbij zie komen.

Acties:
  • 0 Henk 'm!

  • deadinspace
  • Registratie: Juni 2001
  • Laatst online: 06-07 15:56

deadinspace

The what goes where now?

Hmm, hier lijkt alles gewoon te werken zoals ik verwacht:
$ cat settings.sh
#! /bin/bash
echo "Loading settings..."
export PATH=.:$PATH

$ cat test.sh
#! /bin/bash
echo args: $@

$ preload() { (source settings.sh && "$@" ); }
$ echo $PATH
/usr/local/bin:/usr/bin:/bin:/usr/local/games:/usr/games:/usr/local/sbin:/usr/sbin:/sbin

$ test.sh
bash: test.sh: command not found

$ preload test.sh foo bar
Loading settings...
args: foo bar

$ echo $PATH
/usr/local/bin:/usr/bin:/bin:/usr/local/games:/usr/games:/usr/local/sbin:/usr/sbin:/sbin


Hoe is je functie nu exact gedefinieerd?

Acties:
  • 0 Henk 'm!

Anoniem: 474628

Topicstarter
Precies zoals je zei:

C:
1
2
3
preload () {
  (source /opt/software/settings.sh && "$@")
}

Geprobeerd met en zonder ; achter het haakje sluiten. Ook de één-regel versie geprobeerd: niets. Maar hoe kan het dat zonder argument de source wel wordt uitgevoerd?

[ Voor 26% gewijzigd door Anoniem: 474628 op 12-03-2013 16:23 ]


Acties:
  • 0 Henk 'm!

  • deadinspace
  • Registratie: Juni 2001
  • Laatst online: 06-07 15:56

deadinspace

The what goes where now?

Hmm, vreemd. Misschien gaat er iets mis in settings.sh waardoor die vroegtijdig afgebroken wordt. Voeg eens een "echo test" oid toe helemaal bovenaan settings.sh?

Verder: werkt het wel als je
source /opt/software/settings.sh && software project.file

handmatig in bash typt?

En tot slot even om het zeker te weten: gebruik je wel echt bash en niet misschien een andere shell? Controleer desnoods met echo $SHELL en bash --version.

Acties:
  • 0 Henk 'm!

Anoniem: 474628

Topicstarter
deadinspace schreef op dinsdag 12 maart 2013 @ 16:52:
Verder: werkt het wel als je
source /opt/software/settings.sh && software project.file

handmatig in bash typt?
Dat werkt ja. Ook als ik het in een subshell doe:
(source /opt/software/settings.sh && software project.file)
Hmm, vreemd. Misschien gaat er iets mis in settings.sh waardoor die vroegtijdig afgebroken wordt. Voeg eens een "echo test" oid toe helemaal bovenaan settings.sh?
[..]
En tot slot even om het zeker te weten: gebruik je wel echt bash en niet misschien een andere shell? Controleer desnoods met echo $SHELL en bash --version.
En dan komt de aap uit de mouw: je hebt gelijk. echo TEST op de eerste regel wordt wel gewoon uitgevoerd, maar kennelijk de rest niet meer. Ik gebruik wel echt bash, al doet Xilinx (het bedrijf van de software die het settings.sh script levert) wel eens ranzig en roepen ze sh aan terwijl ze bash constructies gebruiken, misschien heeft het daar mee te maken, dat moet ik dan maar even goed nazoeken.

Maar wat kan een anders prima script laten crashen als je het aanroept in een subshell? En nog steeds: zonder argument lijkt het hele script te voltooien (getest met echo EINDE op de laatste regel).

Edit: iets gevonden! In het settings script wordt if [ $# != 0 ]; then aangeroepen, dat voor mij naar false moet evalueren, maar het is true als ik het preload script gebruik. Waarom zou dat zijn?

[ Voor 10% gewijzigd door Anoniem: 474628 op 12-03-2013 17:20 ]


Acties:
  • 0 Henk 'm!

  • deadinspace
  • Registratie: Juni 2001
  • Laatst online: 06-07 15:56

deadinspace

The what goes where now?

Anoniem: 474628 schreef op dinsdag 12 maart 2013 @ 17:12:
Edit: iets gevonden! In het settings script wordt if [ $# != 0 ]; then aangeroepen, dat voor mij naar false moet evalueren, maar het is true als ik het preload script gebruik. Waarom zou dat zijn?
Ahja. Dat wordt interessant :P

$# is het aantal argumenten dat is meegegeven bij de aanroep van het script in kwestie. Het settings.sh script controleert dus of het argumenten meekreeg of niet. Op zich prima, maar omdat het gesourced wordt (in plaats van aangeroepen wordt als "los" commando) krijgt jouw aanroep van settings.sh dus de argumenten van preload mee (namelijk "software" "project.file"). En dat zijn 2 argumenten, niet 0.

Één manier om daaromheen te werken is om de argumenten op te slaan in een andere variabele en de argumenten te wissen voor je settings.sh sourced, zo bijvoorbeeld:
C:
1
2
3
4
5
6
7
#!/bin/bash 
preload () { 
  (COMMANDS="$@"
   shift $#
   source /opt/software/settings.sh && $COMMANDS
  ) 
}

Het grote nadeel van deze aanpak is dat dit misgaat als de argumenten van preload spaties bevatten ("soft ware" of "project file" bv), want "$@" is een beetje speciaal en niet helemaal op deze manier te vangen. Maar ik kan zo snel geen betere manier bedenken waarvoor je settings.sh niet hoeft aan te passen.

Wat doet settings.sh eigenlijk als $# != 0 ?

Acties:
  • 0 Henk 'm!

Anoniem: 474628

Topicstarter
Maar waarom gebeurd dat alleen als ik het in een alias doe, en niet als ik hetzelfde commando/constructie direct in bash intik? Dat snap ik nog niet helemaal.

Anyway, ik ga je suggesties morgen proberen (en uitgebreider beantwoorden). Thanks!

Acties:
  • 0 Henk 'm!

  • deadinspace
  • Registratie: Juni 2001
  • Laatst online: 06-07 15:56

deadinspace

The what goes where now?

Anoniem: 474628 schreef op woensdag 13 maart 2013 @ 00:54:
Maar waarom gebeurd dat alleen als ik het in een alias doe, en niet als ik hetzelfde commando/constructie direct in bash intik? Dat snap ik nog niet helemaal.
Omdat in een functie $# het aantal argumenten van de functie bevat, en op de command-line $# altijd 0 is (omdat je interactieve shell geen argumenten had):
$ echo $# bar
0 bar

$ foo() { echo $#; }
$ foo bar
1


Merk trouwens op dat een functie (wat jij gebruikt) en een alias zeker niet hetzelfde zijn. Een functie is precies dat: een stuk code dat je aan kunt roepen en dat iets voor je doet en wat teruggeeft. Een alias is meer een macro, een soort letterlijke search&replace op je interactieve command-line.

En dat brengt me op een idee; een alias doet waarschijnlijk wel precies wat jij wil, omdat het met een alias net is alsof je het zelf ingetypt hebt! Probeer eens:
alias preload='source /opt/software/settings.sh && '

Acties:
  • 0 Henk 'm!

Anoniem: 474628

Topicstarter
deadinspace schreef op woensdag 13 maart 2013 @ 10:10:
En dat brengt me op een idee; een alias doet waarschijnlijk wel precies wat jij wil, omdat het met een alias net is alsof je het zelf ingetypt hebt! Probeer eens:
alias preload='source /opt/software/settings.sh && '
Ik wilde bijna zeggen dat het werkte, maar nu opent de software natuurlijk niet meer in een subshell... Met de alias-oplossing wordt de environment van mijn huidige shell ook aangepast en dat heb ik liever niet.
deadinspace schreef op woensdag 13 maart 2013 @ 10:10:
[...]

Omdat in een functie $# het aantal argumenten van de functie bevat, en op de command-line $# altijd 0 is (omdat je interactieve shell geen argumenten had):
Maar ik bedoel dan ook vooral waarom het settings-script vindt dat hij argumenten heeft, want die wordt toch ook via de gedefinieerde preload functie zonder argumenten aangeroepen? Maar misschien weet ik dan niet precies hoe source werkt oid.
edit:
Inderdaad, ik heb het even voor mezelf nagezocht en source voert het script uit in de "huidige shell omgeving" en dus zal het de aan preload meegegeven parameters zien.
deadinspace schreef op dinsdag 12 maart 2013 @ 22:33:
[...]
Één manier om daaromheen te werken is om de argumenten op te slaan in een andere variabele en de argumenten te wissen voor je settings.sh sourced, zo bijvoorbeeld:
C:
1
2
3
4
5
6
7
#!/bin/bash 
preload () { 
  (COMMANDS="$@"
   shift $#
   source /opt/software/settings.sh && $COMMANDS
  ) 
}

Het grote nadeel van deze aanpak is dat dit misgaat als de argumenten van preload spaties bevatten ("soft ware" of "project file" bv), want "$@" is een beetje speciaal en niet helemaal op deze manier te vangen. Maar ik kan zo snel geen betere manier bedenken waarvoor je settings.sh niet hoeft aan te passen.
Dit lijk in eerste instantie wel te werken, en misschien moet ik het hier dan maar even mee doen. Over het algemeen probeer ik uiteraard geen spaties in filenames te gebruiken. Mocht ik ooit lastigere commando's willen aanroepen dan moet ik maar gewoon in een interactieve shell settings.sh sourcen.
Wat doet settings.sh eigenlijk als $# != 0 ?
Het lijkt er op dat als je een argument meegeeft, dat het script niet meer automatisch gaat zoeken naar de juiste installatie mappen, maar er van uitgaat dat die in het argument staan. Daarom loopt er uiteraard van alles in het honderd, want hij kan de juiste mappen niet meer vinden.

[ Voor 57% gewijzigd door Anoniem: 474628 op 13-03-2013 10:53 ]


Acties:
  • 0 Henk 'm!

  • deadinspace
  • Registratie: Juni 2001
  • Laatst online: 06-07 15:56

deadinspace

The what goes where now?

Anoniem: 474628 schreef op woensdag 13 maart 2013 @ 10:21:
Ik wilde bijna zeggen dat het werkte, maar nu opent de software natuurlijk niet meer in een subshell... Met de alias-oplossing wordt de environment van mijn huidige shell ook aangepast en dat heb ik liever niet.
Owja, grrrr.
Maar ik bedoel dan ook vooral waarom het settings-script vindt dat hij argumenten heeft, want die wordt toch ook via de gedefinieerde preload functie zonder argumenten aangeroepen? Maar misschien weet ik dan niet precies hoe source werkt oid.
edit:
Inderdaad, ik heb het even voor mezelf nagezocht en source voert het script uit in de "huidige shell omgeving" en dus zal het de aan preload meegegeven parameters zien.
Dat inderdaad. Het feit dat source het script in de huidige shell "opneemt" als het ware is ook de reden dat een gesourced script de omgevingsvariabelen aan kan passen (wat juist het hele doel was).

Overigens is dit ook een beetje irritante semantiek van source, want als je "source script.sh argumenten" doet, dan worden $@ en $# wel overridden. Maar er lijkt geen manier te zijn om dat met 0 argumenten (dus $#=0 en lege $@) te doen...
Dit lijk in eerste instantie wel te werken, en misschien moet ik het hier dan maar even mee doen. Over het algemeen probeer ik uiteraard geen spaties in filenames te gebruiken. Mocht ik ooit lastigere commando's willen aanroepen dan moet ik maar gewoon in een interactieve shell settings.sh sourcen.
Ok, ik heb voor de gelegenheid even gekeken of het met bash arrays wat degelijker op te lossen is, en dat is inderdaad zo! En nog niet eens zo heel moeilijk. Door $@ te backuppen in een array (in plaats van een string, wat shell variabelen standaard zijn) behoud je de whitespace in argumenten wel:
C:
1
2
3
4
5
6
7
#! /bin/bash
preload () {  
  (COMMANDS=("$@")
   shift $#
   source /opt/software/settings.sh && "${COMMANDS[@]}"
  )  
}

Wat een gedoe voor zoiets simpels :P
Maargoed, we komen in ieder geval ergens ;)
Het lijkt er op dat als je een argument meegeeft, dat het script niet meer automatisch gaat zoeken naar de juiste installatie mappen, maar er van uitgaat dat die in het argument staan. Daarom loopt er uiteraard van alles in het honderd, want hij kan de juiste mappen niet meer vinden.
Ahja, dat is nog best wel acceptabel gedrag. Alleen in deze precieze context een beetje onhandig :P

Acties:
  • 0 Henk 'm!

Anoniem: 474628

Topicstarter
deadinspace schreef op woensdag 13 maart 2013 @ 12:48:
[...]

Ok, ik heb voor de gelegenheid even gekeken of het met bash arrays wat degelijker op te lossen is, en dat is inderdaad zo! En nog niet eens zo heel moeilijk. Door $@ te backuppen in een array (in plaats van een string, wat shell variabelen standaard zijn) behoud je de whitespace in argumenten wel:
C:
1
2
3
4
5
6
7
#! /bin/bash
preload () {  
  (COMMANDS=("$@")
   shift $#
   source /opt/software/settings.sh && "${COMMANDS[@]}"
  )  
}
Held! Het werkt in eerste instantie net zo goed als de vorige "backup-oplossing". Met ingewikkeldere argumenten heb ik het nog niet echt getest, want ik heb niet zomaar projecten met rare bestandsnamen gemaakt en dergelijke. Als ik ooit nog tegen iets aanloop laat ik het wel weten (of gebruik ik eventjes geen subshell).
Wat een gedoe voor zoiets simpels :P
Maargoed, we komen in ieder geval ergens ;)
Haha zeker, toen ik de vraag gisteren postte had ik ook zoiets dat het waarschijnlijk binnen vijf minuten een heel simpel antwoord zou worden. Bedankt voor de moeite in ieder geval!

En dan nu nog even gaan stoeien met bash autocomplete zodat de 'nieuwe' software die in je path wordt gezet door settings.sh ge-autocomplete gaat worden als je preload aanroept. Dan wordt het helemaal feest!

Acties:
  • 0 Henk 'm!

  • deadinspace
  • Registratie: Juni 2001
  • Laatst online: 06-07 15:56

deadinspace

The what goes where now?

Anoniem: 474628 schreef op woensdag 13 maart 2013 @ 13:01:
Held! Het werkt in eerste instantie net zo goed als de vorige "backup-oplossing". Met ingewikkeldere argumenten heb ik het nog niet echt getest, want ik heb niet zomaar projecten met rare bestandsnamen gemaakt en dergelijke. Als ik ooit nog tegen iets aanloop laat ik het wel weten (of gebruik ik eventjes geen subshell).
Het is meestal ook praktischer om bestandsnamen zonder spaties e.d. te gebruiken, maar het is natuurlijk wel good practice om te zorgen dat shell scripts daar wel normaal mee om kunnen gaan :)

Ik heb het zelf natuurlijk wel getest:
$ cat settings.sh 
#! /bin/bash
echo "Loading settings... ($# arguments)"
export PATH=.:$PATH

$ cat test.sh 
#! /bin/bash
echo argc: $#
echo args:
for I in "$@"; do echo " - $I"; done

$ preload() { (COMMANDS=("$@"); shift $#; source settings.sh && "${COMMANDS[@]}" ); }
$ preload ./test.sh foo "bar baz"
Loading settings... (0 arguments)
argc: 2
args:
 - foo
 - bar baz
Haha zeker, toen ik de vraag gisteren postte had ik ook zoiets dat het waarschijnlijk binnen vijf minuten een heel simpel antwoord zou worden. Bedankt voor de moeite in ieder geval!
Dat had ik in eerste instantie ook verwacht ja :P
Maargoed, fijn dat je er mee geholpen bent in ieder geval :)

Offtopic:
Als je veel met de commandline werkt en wat geavanceerdere dingen gebruikt, dan wil je misschien eens naar zsh kijken, dat is de shell die ik zelf gebruik en ik vind hem echt heerlijk. Hij heeft onder andere een "bladerfunctie" voor tabcompletion en veel uitgebreidere wildcard-mogelijkheden. Een aanrader als je in de shell leeft ;)
Pagina: 1